如何读取子进程的输出?
Posted
技术标签:
【中文标题】如何读取子进程的输出?【英文标题】:How can I read a child process's output? 【发布时间】:2012-06-03 00:13:38 【问题描述】:我编写了一个函数,它试图通过管道读取子进程的命令行输出。这应该是the MSDN Creating a Child Process with Redirected Input and Output article 的一个简单子集,但我显然犯了某种错误。
下面的 ReadFile(...) 调用将永远阻塞,无论我是在 WaitForSingleObject(...) 调用之前还是之后放置它,这应该标志着子进程的结束。
我已阅读所有建议“使用异步 ReadFile”的答案,如果有人能给我一些想法如何在管道上完成,我愿意接受该建议。虽然我不明白为什么这种情况需要异步 I/O。
#include "stdafx.h"
#include <string>
#include <windows.h>
unsigned int launch( const std::string & cmdline );
int _tmain(int argc, _TCHAR* argv[])
launch( std::string("C:/windows/system32/help.exe") );
return 0;
void print_error( unsigned int err )
char* msg = NULL;
FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&msg,
0, NULL );
std::cout << "------ Begin Error Msg ------" << std::endl;
std::cout << msg << std::endl;
std::cout << "------ End Error Msg ------" << std::endl;
LocalFree( msg );
unsigned int launch( const std::string & cmdline )
TCHAR cl[_MAX_PATH*sizeof(TCHAR)];
memset( cl, 0, sizeof(cl) );
cmdline.copy( cl, (_MAX_PATH*sizeof(TCHAR)) - 1);
HANDLE stdoutReadHandle = NULL;
HANDLE stdoutWriteHandle = NULL;
SECURITY_ATTRIBUTES saAttr;
memset( &saAttr, 0, sizeof(saAttr) );
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT.
if ( ! CreatePipe(&stdoutReadHandle, &stdoutWriteHandle, &saAttr, 5000) )
throw std::runtime_error( "StdoutRd CreatePipe" );
// Ensure the read handle to the pipe for STDOUT is not inherited.
if ( ! SetHandleInformation(stdoutReadHandle, HANDLE_FLAG_INHERIT, 0) )
throw std::runtime_error( "Stdout SetHandleInformation" );
STARTUPINFO startupInfo;
memset( &startupInfo, 0, sizeof(startupInfo) );
startupInfo.cb = sizeof(startupInfo);
startupInfo.hStdError = stdoutWriteHandle;
startupInfo.hStdOutput = stdoutWriteHandle;
startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
startupInfo.dwFlags |= STARTF_USESTDHANDLES;
char* rawEnvVars = GetEnvironmentStringsA();
//__asm _emit 0xcc;
PROCESS_INFORMATION processInfo;
memset( &processInfo, 0, sizeof(processInfo) );
std::cout << "Start [" << cmdline << "]" << std::endl;
if ( CreateProcessA( 0, &cl[0], 0, 0, false,
CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT,
rawEnvVars, 0, &startupInfo, &processInfo ) )
//CloseHandle( stdoutWriteHandle );
DWORD wordsRead;
char tBuf[257] = '\0';
bool success = true;
std::string outBuf("");
unsigned int t;
while(success)
//__asm _emit 0xcc;
std::cout << "Just before ReadFile(...)" << std::endl;
success = ReadFile( stdoutReadHandle, tBuf, 256, &wordsRead, NULL);
(t=GetLastError())?print_error(t):t=t;
std::cout << "Just after ReadFile(...) | read " << wordsRead<< std::endl;
std::cout << ".";
if( success == false ) break;
outBuf += tBuf;
tBuf[0] = '\0';
std::cout << "output = [" << outBuf << "]" << std::endl;
if ( WaitForSingleObject( processInfo.hProcess, INFINITE ) == WAIT_OBJECT_0 )
unsigned int exitcode = 0;
GetExitCodeProcess( processInfo.hProcess, (LPDWORD)&exitcode );
std::cout << "exitcode = [" << exitcode << "]" << std::endl;
//__asm _emit 0xcc;
CloseHandle( processInfo.hProcess );
CloseHandle( processInfo.hThread );
return exitcode;
else
DWORD procErr = GetLastError();
std::cout << "FAILED TO CREATE PROCESS!" << std::endl;
print_error( procErr );
return -1;
// end launch()
【问题讨论】:
你试过popen/_popen吗? 第一次调用 ReadFile 或后续调用时会挂起吗? 回复:@Harry Johnston - 它第一次通过调用 ReadFile(...) 的循环挂起 【参考方案1】:您的代码中有一些错误,但最重要的是您为 CreateProcess
的 bInheritHandles
参数指定了 FALSE
。如果新进程没有继承它的句柄,它就不能使用管道。为了继承句柄,bInheritHandles
参数必须是 TRUE
并且句柄必须启用继承。
其他问题:
您指定了 CREATE_UNICODE_ENVIRONMENT
,但传递了一个 ANSI 环境块。请注意,将NULL
传递给lpEnvironment
并让系统为您复制环境块更容易。在这种情况下,您仍应指定 CREATE_UNICODE_ENVIRONMENT
,如文档中所述,因为您的环境块可能包含 Unicode 字符。
同样,如果您调用 CreateProcessA,您应该使用 STARTUPINFOA。
您不会在循环中每次都以零结尾 tBuf
,因此您会在输出缓冲区中获得虚假的额外字符。
您需要在进入读取循环之前关闭stdoutWriteHandle
,否则您将不知道子进程何时退出。 (或者您可以使用异步 IO 并显式检查进程退出。)
如果 API 函数成功,GetLastError()
是未定义的,所以你应该只在 ReadFile
返回 FALSE
时调用它。 (当然,在这种情况下,这纯粹是装饰性的,因为您没有对错误代码采取行动。)
作为参考,这是我对您的代码的更正版本。我已经把它变成了普通的 C(对不起!),因为那是我所熟悉的。我在 Unicode 模式下编译和测试过,但我认为它也应该在 ANSI 模式下无需修改。
#define _WIN32_WINNT _WIN32_WINNT_WIN7
#include <windows.h>
#include <stdio.h>
void launch(const char * cmdline_in)
PROCESS_INFORMATION processInfo;
STARTUPINFOA startupInfo;
SECURITY_ATTRIBUTES saAttr;
HANDLE stdoutReadHandle = NULL;
HANDLE stdoutWriteHandle = NULL;
char cmdline[256];
char outbuf[32768];
DWORD bytes_read;
char tBuf[257];
DWORD exitcode;
strcpy_s(cmdline, sizeof(cmdline), cmdline_in);
memset(&saAttr, 0, sizeof(saAttr));
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT.
if (!CreatePipe(&stdoutReadHandle, &stdoutWriteHandle, &saAttr, 5000))
printf("CreatePipe: %u\n", GetLastError());
return;
// Ensure the read handle to the pipe for STDOUT is not inherited.
if (!SetHandleInformation(stdoutReadHandle, HANDLE_FLAG_INHERIT, 0))
printf("SetHandleInformation: %u\n", GetLastError());
return;
memset(&startupInfo, 0, sizeof(startupInfo));
startupInfo.cb = sizeof(startupInfo);
startupInfo.hStdError = stdoutWriteHandle;
startupInfo.hStdOutput = stdoutWriteHandle;
startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
startupInfo.dwFlags |= STARTF_USESTDHANDLES;
// memset(&processInfo, 0, sizeof(processInfo)); // Not actually necessary
printf("Starting.\n");
if (!CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, NULL, 0, &startupInfo, &processInfo))
printf("CreateProcessA: %u\n", GetLastError());
return;
CloseHandle(stdoutWriteHandle);
strcpy_s(outbuf, sizeof(outbuf), "");
for (;;)
printf("Just before ReadFile(...)\n");
if (!ReadFile(stdoutReadHandle, tBuf, 256, &bytes_read, NULL))
printf("ReadFile: %u\n", GetLastError());
break;
printf("Just after ReadFile, read %u byte(s)\n", bytes_read);
if (bytes_read > 0)
tBuf[bytes_read] = '\0';
strcat_s(outbuf, sizeof(outbuf), tBuf);
printf("Output: %s\n", outbuf);
if (WaitForSingleObject(processInfo.hProcess, INFINITE) != WAIT_OBJECT_0)
printf("WaitForSingleObject: %u\n", GetLastError());
return;
if (!GetExitCodeProcess(processInfo.hProcess, &exitcode))
printf("GetExitCodeProcess: %u\n", GetLastError());
return;
printf("Exit code: %u\n", exitcode);
CloseHandle( processInfo.hProcess );
CloseHandle( processInfo.hThread );
return;
int main(int argc, char** argv)
launch("C:\\windows\\system32\\help.exe");
return 0;
【讨论】:
你是正确的。 CreateProcess(...) 的 bInheritHandles 参数正是它。对不起其他杂物。其中大部分是愚蠢的调试和问题最小化垃圾。其余的只是不可原谅的草率。 为了将来参考,最安全的方法是使用PROC_THREAD_ATTRIBUTE_HANDLE_LIST
明确指定要继承的句柄。不幸的是,如果您提供明确的句柄列表,文档并不清楚您应该为bInheritHandles
传递什么;可能需要进行实验。
如何在 UNICODE 项目中做到这一点?【参考方案2】:
ReadFile() 有一个“LPOVERLAPPED lpOverlapped”参数,您已将其设置为 NULL。看起来唯一的方法是允许管道上的重叠 I/O,然后将 WaitForSingleObject() 用于“overlapped.hEvent”。
另一种方法是使用 ConnectNamedPipe 函数并为管道创建 OVERLAPPED 结构。
【讨论】:
以上是关于如何读取子进程的输出?的主要内容,如果未能解决你的问题,请参考以下文章