如何在C#中实时捕获原生进程输出?现有代码失效求助
解决原生进程实时输出捕获问题
你的代码无法实时捕获进程输出主要有以下几个关键问题:
1. 管道句柄未设置为可继承
CreatePipe 调用时未指定安全属性,导致创建的管道句柄默认不可被子进程继承。子进程无法获取标准输出的写入句柄,因此输出无法写入管道。
2. 未允许子进程继承句柄
CreateProcessAsUser 调用中 bInheritHandles 参数设为 false,即使管道句柄可继承,子进程也无法获取这些句柄。
3. 读取方式和编码问题
StreamReader.ReadLineAsync()会等待完整换行符才返回,无法实时获取部分行输出- 使用
Encoding.Unicode可能与 cmd.exe 等原生进程的输出编码不匹配(通常为系统默认编码或 OEM 编码)
修正后的关键代码
1. 修正管道创建逻辑(设置可继承句柄)
在 TryCreateInteractiveProcess 方法中,创建管道时添加可继承的安全属性:
private static unsafe bool TryCreateInteractiveProcess(SafeHandle hUserTokenDup, string applicationName, string desktopName, bool hiddenWindow, out PROCESS_INFORMATION procInfo, out SafeFileHandle stdOutputReadHandle) { // 创建可继承的安全属性 var sa = new SECURITY_ATTRIBUTES(); sa.nLength = (uint)Marshal.SizeOf(sa); sa.bInheritHandle = true; sa.lpSecurityDescriptor = null; if (!CreatePipe(out stdOutputReadHandle, out SafeFileHandle stdOutputWriteHandle, ref sa, 0)) { throw new Exception("Failed to create pipe for standard output."); } fixed (char* pDesktopName = $@"winsta0\{desktopName}") { var startupInfo = new STARTUPINFOW { cb = (uint)Marshal.SizeOf<STARTUPINFOW>(), lpDesktop = pDesktopName, hStdOutput = (HANDLE)stdOutputWriteHandle.DangerousGetHandle(), hStdError = (HANDLE)stdOutputWriteHandle.DangerousGetHandle(), dwFlags = STARTUPINFOW_FLAGS.STARTF_USESTDHANDLES }; var dwCreationFlags = SetCreationFlags(hiddenWindow); applicationName += char.MinValue; var appName = new Span<char>(applicationName.ToCharArray()); // 将 bInheritHandles 设为 true var result = CreateProcessAsUser(hUserTokenDup, null, ref appName, null, null, true, dwCreationFlags, null, null, startupInfo, out procInfo); stdOutputWriteHandle.Close(); return result; } }
2. 修正输出读取逻辑(实时捕获分块输出)
修改 StartListeningToOutputAsync 方法,改用分块读取并使用正确的编码:
public async Task StartListeningToOutputAsync(CancellationToken cancellationToken = default) { if (StdOutputReadHandle == null || StdOutputReadHandle.IsInvalid) { throw new InvalidOperationException("Invalid standard output handle."); } using var fs = new FileStream(StdOutputReadHandle, FileAccess.Read, 4096, false); // 使用系统默认编码匹配原生进程输出 using var reader = new StreamReader(fs, Encoding.Default); var buffer = new char[1024]; int charsRead; while (!cancellationToken.IsCancellationRequested && (charsRead = await reader.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0) { var output = new string(buffer, 0, charsRead); OutputReceived?.Invoke(output); } }
3. 测试代码调整(可选)
如果希望测试脚本执行后自动退出,将 cmd.exe /k 改为 cmd.exe /c,避免进程持续运行导致读取循环无法结束:
var options = new ProcessStartOptions($"cmd.exe /c \"{scriptPath}\"", -1) { ForceConsoleSession = true, DesktopName = "default", HiddenWindow = true, UseCurrentUserToken = true };
额外说明
- 若需要支持交互式进程(如持续运行的 cmd 会话),需添加取消令牌逻辑以主动终止读取循环
- 对于不同的原生进程,可能需要调整编码(如 UTF-8)以匹配其输出格式
内容的提问来源于stack exchange,提问作者vitkuz573
相关产品推荐
相关产品推荐

