【问题标题】:How do I read stdout/stderr output of a child process correctly?如何正确读取子进程的 stdout/stderr 输出?
【发布时间】:2011-03-11 00:06:41
【问题描述】:

我编写了一个程序a.exe,它使用CreateProcess 函数启动了我编写的另一个程序b.exe。调用者创建两个管道并将两个管道的写入端作为标准输出/标准错误句柄传递给 CreateProcess,以供子进程使用。这与 MSDN 上的 Creating a Child Process with Redirected Input and Output 示例几乎相同。

因为它似乎无法使用一个同步调用来等待进程退出 stdout 或 stderr 上的数据可用(WaitForMultipleObjects 函数不在管道上工作),调用者有两个正在运行的线程,它们都在 stdout/stderr 管道的读取端执行(阻塞)ReadFile 调用;这是用于 stdout/stderr 的“读取线程过程”的确切代码(我自己没有编写此代码,我假设某些同事做了):

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;
}

a.exe 然后使用简单的WaitForSingleObject 调用等待b.exe 终止。一旦该调用返回,两个读取线程就会终止(因为管道已损坏)并使用CloseHandle 关闭两个管道的读取端。

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

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

更新:这是我目前尝试过的:

  1. 没有b.exe继承a.exe;这不起作用。 MSDN 特别指出传递给 CreateProcess 的句柄必须是可继承的。
  2. 清除 b.exe 内部 stdout/stderr 上的可继承标志:似乎没有任何效果(如果有的话,我会感到惊讶)。
  3. ReadDataProc 过程(在两个管道上读取)除了检查ERROR_BROKEN_PIPE 之外,还要考虑b.exe 是否实际运行。这当然不起作用(但我后来才意识到)因为线程在 ReadFile 调用中被阻塞了。

【问题讨论】:

  • 也许试试 CancelSynchronousIO ?
  • @adf88:根据WaitForMultipleObjects 上的 MSDN 页面,它可以处理各种事情——但不是管道。我的实验似乎证实了这一点:管道的读取端总是发出信号,即使没有可用数据。 CancelSynchronousIO() 函数看起来不错,但它不适合我,因为它仅适用于 Windows Vista 和更新版本。
  • @adf88:你引用的问题看起来很相似,但它不是这个问题的重复。我面临同样的症状,但我在比其他问题更广泛的解决方案空间中寻求解决方案。
  • b.exeFALSE 传递给 CreateProcess() 的 bInheritHandles 参数就足够了。

标签: c++ windows winapi process


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    似乎在 Windows Vista 之前的 Windows 版本上(您可以使用 CancelSynchronousIO 函数,没有办法使用 TerminateThread 终止读取线程。

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

    【讨论】:

      【解决方案3】:

      在这种情况下发生的事情是 写入标准输出/标准错误的结尾 管道被继承到该守护进程 过程,因此管道永远不会破裂。

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

      【讨论】:

      • +1:这是一个有趣的观点;值得一提的是,b.exe 启动的守护程序是客户的程序。然而,在责备他之前,我想有更多的论据来支持这一说法。您是否碰巧有一些解释的链接,或者您可以将您的回复延长一点?
      • 成为守护进程意味着进行一些操作(“守护进程”)并关闭继承的文件描述符是其中之一(在 UNIX 上,它们通常从/到/dev/null 重定向而不是关闭)。我相信每本关于 UNIX 系统编程的书中都讲授了这一点(是的,我记得你关心的是 windows,但守护进程的概念最初来自 UNIX)。 (因篇幅限制拆分评论)
      • 对于在线参考,您可以使用维基百科文章secure.wikimedia.org/wikipedia/en/wiki/… 并引用它包含的内容。您可以在 Google 上搜索很多关于编写守护进程的 HOWTO,例如:cs.aau.dk/~adavid/teaching/MTP-05/exercises/10/…(第 4.6 节“关闭标准文件描述符”)
      • 顺便说一句,如果你不想惹你的客户,你可以自己关闭文件描述符。在 UNIX 上,这看起来像:fork(创建当前进程的副本);在子进程中:关闭文件描述符(或重定向它们); exec "b.exe"(在当前(子)进程中执行b.exe的代码)。由于 windows 缺少 fork/exec 调用,因此可能存在与此操作序列等价的操作。
      • Windows 有一个 SetHandleInformation((HANDLE)theSockFD, HANDLE_FLAG_INHERIT, 0) 调用,可用于确保特定套接字不会被生成的子进程继承。不过,我认为 b.exe 的作者将是需要调用它的那个人。
      【解决方案4】:
      1. 使用命名管道和异步 ReadFile

      2. 解析从管道读取的输出以查找结尾(在您的情况下可能太复杂了)。

      【讨论】:

      • +1:解析管道输出,寻找“进程结束”标记,是一个创意。 ;-)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-07-29
      • 1970-01-01
      • 2018-03-16
      • 1970-01-01
      • 1970-01-01
      • 2013-11-08
      • 2016-12-21
      相关资源
      最近更新 更多