【问题标题】:Windows: How to stop buffering of redirected Stdout using CreateProcessWindows:如何使用 CreateProcess 停止缓冲重定向的标准输出
【发布时间】:2016-12-05 00:38:30
【问题描述】:

我正在使用管道从命令行可执行文件中获取重定向的 stdout 输出。不幸的是,在该过程完成之前我没有得到任何输出。可执行文件在运行时输出进度状态,这是我想要解析的。

BOOL RunCmd( char  *pCmd, 
             char  *pWorkingDir, 
             int    nWaitSecs, 
             BOOL   fRegImport, 
             DWORD *pdwExitCode )
{
  BOOL                fSuccess = TRUE;
  STARTUPINFO         si;
  PROCESS_INFORMATION pi;
  SECURITY_ATTRIBUTES sFileSecurity;
  ZeroMemory( &sFileSecurity, sizeof( sFileSecurity ) );
  sFileSecurity.nLength        = sizeof( sFileSecurity );
  sFileSecurity.bInheritHandle = TRUE;

  HANDLE hReadPipe  = NULL;
  HANDLE hWritePipe = NULL;

  fSuccess = CreatePipe( &hReadPipe, &hWritePipe, &sFileSecurity, 0 );
  SetHandleInformation( hReadPipe, HANDLE_FLAG_INHERIT, 0 ); 

  ZeroMemory( &si, sizeof(si) );
  ZeroMemory( &pi, sizeof(pi) );
  si.cb          = sizeof( si );
  si.dwFlags     = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
  si.hStdOutput  = hWritePipe;
  si.hStdError   = hWritePipe;
  si.wShowWindow = SW_HIDE;

  int rc;

  // Start the child process. 
  rc = CreateProcess( NULL, // No module name (use command line). 
                      pCmd, // Command line. 
                      NULL,             // Process handle not inheritable. 
                      NULL,             // Thread handle not inheritable. 
                      TRUE,
                      CREATE_NO_WINDOW,
                      NULL,             // Use parent's environment block. 
                      pWorkingDir,      // Working folder
                      &si,              // Pointer to STARTUPINFO structure.
                      &pi );            // Pointer to PROCESS_INFORMATION structure.


  if( ! rc )
    return FALSE;

  // Wait until child process exits.
  DWORD dwWaitResult;
  DWORD dwTimeStart = ::GetTickCount();
  DWORD dwTimeNow;

  #define BUFSIZE 4096  

  DWORD dwRead = 0; 
  DWORD dwAvail;
  CHAR  chBuf[ BUFSIZE ];
  BOOL  bSuccess = TRUE;

  for( ;; )
  {
    dwTimeNow    = ::GetTickCount();
    dwWaitResult = ::WaitForSingleObject( pi.hProcess, ONE_SECOND );
    dwRead       = 0;

    for( dwAvail = 0; PeekNamedPipe( hReadPipe, 0, 0, 0, &dwAvail, 0 ) && dwAvail; dwAvail = 0 )
    {
      dwRead = 0;
      ReadFile( hReadPipe, chBuf, min( BUFSIZE, dwAvail ), &dwRead, NULL );

      if( dwRead > 0 )
      {
        FILE *op = fopen( "c:\\REDIR.OUT", "a" );

        if( op )
        {
          fwrite( chBuf, 1, dwRead, op );
          fclose( op );
        }
      }
    }

    if( dwWaitResult == WAIT_OBJECT_0 )
    {
      DWORD dwExitCode;
      GetExitCodeProcess( pi.hProcess, &dwExitCode );

      if( pdwExitCode )
        (*pdwExitCode) = dwExitCode;

      break;
    }

    if( dwWaitResult == WAIT_TIMEOUT )
    {
      if( dwTimeNow - dwTimeStart < (DWORD)( ONE_SECOND * nWaitSecs ) )
        continue;
      else
      {
        fSuccess = FALSE;
        break;
      }
    }

    fSuccess = FALSE;
    break;
  }

  CloseHandle( pi.hProcess );
  CloseHandle( pi.hThread );
  CloseHandle( hReadPipe );
  CloseHandle( hWritePipe );

  return fSuccess;
}

PeekNamedPipe() 调用每秒被调用一次,并且 dwAvail 每次都为零,直到进程完成。

如何更快地从流程中获得输出?从控制台运行该过程时,我会看到进度输出。该过程将在其输出中使用“\r”在同一行的开头显示百分比。

【问题讨论】:

  • 缓冲在另一个应用程序中,而不是在操作系统中。您将不得不修改您正在启动的进程以告诉它禁用缓冲。
  • @RaymondChen 谢谢 - 这是我最大的恐惧。我们没有那个来源。想一想,当我们将它作为 Visual Studio“外部工具”运行时,我已经看到它缓冲了它的输出。您会认为如果 CMD.exe 能够显示其所有进度更新,那么包装程序将能够执行相同的操作。
  • @RaymondChen 是的,以下链接描述了问题:support.microsoft.com/en-nz/kb/190351
  • 那是因为 cmd 将 stdout 连接到控制台。大多数应用程序会检测标准输出是否连接到控制台,如果没有连接到缓冲区。这是操作系统无法控制的内部应用逻辑。

标签: windows winapi process stdout win32-process


【解决方案1】:

注意:我的回答只涉及使用 MSVC 编译的可执行文件。

缓冲策略编码在 Microsoft C 运行时 (CRT) 库中。您可以了解详情here。本文建议使用控制台句柄和操作控制台缓冲区来接收无缓冲的输出。

但是,Microsoft C 运行时中有一个未记录的功能,可以使用lpReserved2 和STARTUPINFO 结构的lpReserved2 和cbReserved2 字段直接从其父进程继承带有一些内部标志的文件句柄。您可以在 Microsoft Visual Studio 提供的 crt 源代码中找到详细信息。或者在 GitHub 上搜索 posfhnd 之类的内容。

我们可以利用这个未记录的特性来提供一个管道句柄并为子进程指定FOPEN | FDEV 标志,以欺骗子进程将管道句柄与FILE_TYPE_CHAR 句柄一样对待。

我有一个有效的Python3 script 来演示这个方法。

【讨论】:

    猜你喜欢
    • 2017-09-02
    • 2010-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-09
    • 1970-01-01
    • 2014-02-18
    相关资源
    最近更新 更多