2016-12-05 32 views
1

我正在使用管道从命令行可执行文件获取重定向stdout输出。不幸的是,我没有得到任何输出,直到过程完成。可执行文件在运行时输出进度状态,这就是我想要分析的内容。Windows:如何使用CreateProcess停止缓冲重定向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”在同一行的开头显示百分比。

+4

缓冲区在另一个应用程序中,而不在OS中。你将不得不修改你正在启动的进程,告诉它禁用缓冲。 –

+0

@RaymondChen谢谢 - 这是我最大的恐惧。我们没有这个来源。想想看,当我们将它作为Visual Studio“外部工具”运行时,我已经看到它缓冲它的输出。你会想,如果CMD.exe能够显示所有的进度更新,那么一个包装程序将能够做同样的事情。 – SparkyNZ

+0

@RaymondChen是的,下面的链接描述了这个问题:https://support.microsoft.com/en-nz/kb/190351 – SparkyNZ

回答

0

注:我的回答只处理使用MSVC编译的可执行文件。

缓冲策略在Microsoft C运行时(CRT)库内编码。您可以了解详细信息here。本文建议使用控制台句柄并操纵控制台缓冲区以接收无缓冲的输出。

然而,有Microsoft C运行时内无证功能继承文件与一些内部直接使用STARTUPINFO结构的lpReserved2cbReserved2领域其父进程的标志处理。您可以在Microsoft Visual Studio提供的crt源代码中找到详细信息。或者在GitHub上搜索类似posfhnd的内容。

我们可以利用这个未公开的特性来提供一个管道句柄,并为子进程指定FOPEN | FDEV标志来欺骗子进程,将这个管道的处理方式与FILE_TYPE_CHAR句柄相同。

我有一个工作Python3 script来演示这种方法。