Windows:如何使用CreateProcess停止重定向标准输出的缓冲

ddhy6vgd  于 2023-01-18  发布在  Windows
关注(0)|答案(2)|浏览(143)

我正在使用管道从命令行可执行文件中获取重定向的标准输出输出。很遗憾,在进程完成之前,我没有得到任何输出。可执行文件在运行时输出进度状态,这就是我要分析的内容。

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”在同一行的开头显示百分比。

o4hqfura

o4hqfura1#

注意:我的答案只涉及使用MSVC编译的可执行文件。
缓冲策略是在Microsoft C运行时(CRT)库中编写的。您可以了解here的详细信息。本文建议使用控制台句柄并操作控制台缓冲区来接收未缓冲的输出。
但是,Microsoft C Runtime中有一个未公开的特性,可以使用STARTUPINFO结构的lpReserved2cbReserved2字段直接从父进程继承带有一些内部标志的文件句柄。您可以在Microsoft Visual Studio提供的crt源代码中找到详细信息。或者在GitHub上搜索类似posfhnd的内容。
我们可以利用这个未公开的特性来提供管道句柄并为子进程指定FOPEN | FDEV标志,以欺骗子进程将该管道句柄视为FILE_TYPE_CHAR句柄。
我有一个工作的Python3 script来演示这个方法。

qij5mzcb

qij5mzcb2#

@youfu的答案的简化C/C++版本。

STARTUPINFO si;
int nh = 2;
si.cbReserved2 = (WORD)(sizeof(int) + (nh *
                          (sizeof(char) + sizeof(HANDLE))));

si.lpReserved2 = (LPBYTE) calloc(si.cbReserved2, 1);

*((UNALIGNED int *)(si.lpReserved2)) = nh;

unsigned char* posfile = (unsigned char *)(si.lpReserved2 + sizeof(int));

UNALIGNED HANDLE* posfhnd = (UNALIGNED HANDLE *)(si.lpReserved2 + sizeof(int) +
          (nh * sizeof(unsigned char)));

unsigned char FOPEN = 0x01;
unsigned char FDEV  = 0x40;
*posfile = FOPEN | FDEV;
posfile++;
*posfile = FOPEN | FDEV;

*posfhnd = child_stdin_rd;
posfhnd++;
*posfhnd = child_stdout_wr;
  • -苏拉杰

相关问题