Windows:如何使用CreateProcess停止缓冲重定向Stdout
问题描述:
我正在使用管道从命令行可执行文件获取重定向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”在同一行的开头显示百分比。
答
注:我的回答只处理使用MSVC编译的可执行文件。
缓冲策略在Microsoft C运行时(CRT)库内编码。您可以了解详细信息here。本文建议使用控制台句柄并操纵控制台缓冲区以接收无缓冲的输出。
然而,有Microsoft C运行时内无证功能继承文件与一些内部直接使用STARTUPINFO
结构的lpReserved2
和cbReserved2
领域其父进程的标志处理。您可以在Microsoft Visual Studio提供的crt源代码中找到详细信息。或者在GitHub上搜索类似posfhnd
的内容。
我们可以利用这个未公开的特性来提供一个管道句柄,并为子进程指定FOPEN | FDEV
标志来欺骗子进程,将这个管道的处理方式与FILE_TYPE_CHAR
句柄相同。
我有一个工作Python3 script来演示这种方法。
缓冲区在另一个应用程序中,而不在OS中。你将不得不修改你正在启动的进程,告诉它禁用缓冲。 –
@RaymondChen谢谢 - 这是我最大的恐惧。我们没有这个来源。想想看,当我们将它作为Visual Studio“外部工具”运行时,我已经看到它缓冲它的输出。你会想,如果CMD.exe能够显示所有的进度更新,那么一个包装程序将能够做同样的事情。 – SparkyNZ
@RaymondChen是的,下面的链接描述了这个问题:https://support.microsoft.com/en-nz/kb/190351 – SparkyNZ