C++ 如何正确读取子进程的stdout/stderr输出?

C++ 如何正确读取子进程的stdout/stderr输出?,c++,windows,winapi,process,C++,Windows,Winapi,Process,我已经编写了一个程序a.exe,它使用函数启动我编写的另一个程序b.exe。调用者创建两个管道,并将两个管道的写入端作为stdout/stderr句柄传递给CreateProcess,以用于子进程。这实际上与MSDN上的样本相同 由于它似乎无法使用一个同步调用来等待进程退出或stdout或stderr上的数据可用(该函数在管道上不工作),因此调用方有两个线程正在运行,这两个线程都在stdout/stderr管道的读取端执行(阻塞)调用;下面是用于stdout/stderr的“read threa

我已经编写了一个程序
a.exe
,它使用函数启动我编写的另一个程序
b.exe
。调用者创建两个管道,并将两个管道的写入端作为stdout/stderr句柄传递给CreateProcess,以用于子进程。这实际上与MSDN上的样本相同

由于它似乎无法使用一个同步调用来等待进程退出或stdout或stderr上的数据可用(该函数在管道上不工作),因此调用方有两个线程正在运行,这两个线程都在stdout/stderr管道的读取端执行(阻塞)调用;下面是用于stdout/stderr的“read thread procedure”的确切代码(我不是自己写这段代码的,我想是有同事写的):

a.exe
然后使用一个简单的调用等待
b.exe
终止。调用返回后,两个读取线程终止(因为管道断开),并使用关闭两个管道的读取端

现在,我遇到的问题是:
b.exe
可能(取决于用户输入)启动比
b.exe
本身寿命更长的外部进程,基本上是守护进程。在这种情况下,stdout/stderr管道的写入端被继承到该守护进程,因此管道永远不会中断。这意味着
a.exe
中的WaitForSingleObject调用将返回(因为
b.exe
已完成),但其中一个管道上的CloseHandle调用将被阻塞,因为两个读取线程仍处于其(阻塞!)ReadFile调用中

在返回
b.exe
后,如何在不使用蛮力()终止两个读取线程的情况下解决此问题?如果可能,我希望避免任何涉及管道轮询和/或流程轮询的解决方案

更新:以下是我迄今为止尝试的内容:

  • 没有
    b.exe
    继承
    a.exe
    ;这不管用。MSDN特别指出传递给CreateProcess的句柄必须是可继承的
  • 清除stdout/stderr内的可继承标志
    b.exe
    :似乎没有任何效果(如果有,我会感到惊讶)
  • 具有<代码> Read DATA PROC 过程(在两个管道上读取)考虑是否<代码> B.EXE 除了检查<代码> ErrRoBROKEN管道> /COD>之外,实际上还在运行。这当然不起作用(但我后来才意识到),因为线程在ReadFile调用中被阻塞了
  • 使用命名管道和异步读取文件

  • 解析从管道读取的输出,寻找结束(在您的情况下可能太复杂)
  • 在这种情况下发生的是 stdout/stderr的写入端 管道被继承到该守护进程 过程,所以管道永远不会破裂


    守护进程应该关闭其继承的文件描述符。

    似乎在Windows Vista之前的Windows版本上(您可以使用该功能的地方),无法使用终止读取线程


    一个合适的选择()可能是使用异步调用,但在我的情况下这是不可能的(需要对现有代码进行太多更改)。

    设置一些全局标志(bool exit_标志)并在a.exe中向管道写入内容

    也许可以尝试CancelSynchronousIO?@adf88:根据
    WaitForMultipleObjects
    上的MSDN页面,它可以处理所有类型的事情,但不能处理管道。我的实验似乎证实了这一点:管道的读取端总是有信号,即使没有可用数据。CancelSynchronousIO()函数看起来不错,但它不是我的选项,因为它只在Windows Vista和更高版本上可用。可能重复:@adf88:你提到的问题看起来类似,但它不是这个问题的重复。我面临着同样的症状,但我要在比其他问题更广阔的解决空间中寻求解决方案。应该是这样的足够
    b.exe
    FALSE
    传递给CreateProcess()的
    bInheritHandles
    参数.1:这是一个有趣的观点;值得一提的是,
    b.exe
    启动的守护程序是一个客户的程序。然而,在责怪他之前,我想有更多的论据来支持这一说法。你是否碰巧有一些解释的链接,或者你可以稍微扩展一下你的回答?成为守护程序意味着什么s进行一些操作(“守护进程化”)并关闭继承的文件描述符就是其中之一(在UNIX上,它们通常从/重定向到
    /dev/null
    ,而不是关闭)。我相信这在每本关于UNIX系统编程的书中都有教过(是的,我记得你关心windows,但守护进程的概念最初来自UNIX)。(由于长度限制而拆分注释)对于在线参考,您可以使用Wikipedia文章及其包含的引用。您可以在谷歌上搜索许多编写守护进程的方法,如:(第4.6节“关闭标准文件描述符”)顺便说一句,如果你不想打扰你的客户,你可以自己关闭文件描述符。在UNIX上这看起来像:fork(创建当前进程的副本);在子进程中:关闭文件描述符(或重定向);exec“b.exe”(在当前(子)进程中执行b.exe的代码)。由于windows缺少fork/exec调用,因此可能存在类似于此操作序列的操作。windows有一个setHandle信息((HANDLE)sockfd,HANDLE\u FLAG\u INHERIT,0)调用,您可以使用它来确保特定套接字不会被生成的子进程继承。不过,我认为b.exe的作者将需要调用它。+1:解析管道输出,寻找“进程结束”标记,这是一个创造性的想法。;-)
    DWORD __stdcall ReadDataProc( void *handle )
    {
        char buf[ 1024 ];
        DWORD nread;
        while ( ReadFile( (HANDLE)handle, buf, sizeof( buf ), &nread, NULL ) &&
                GetLastError() != ERROR_BROKEN_PIPE ) {
            if ( nread > 0 ) {
                fwrite( buf, nread, 1, stdout );
            }
        }
        fflush( stdout );
        return 0;
    }