You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 16:10:53