How can I read a child process's output?
c, pipe, winapi, windows
Solution
There are a few bugs in your code, but the most important is that you've specified `FALSE` for the `bInheritHandles` argument to `CreateProcess`. The new process can't use the pipe if it doesn't inherit the handle to it. In order for a handle to be inherited, the `bInheritHandles` argument must be `TRUE` and the handle must have inheritance enabled.
Other issues:
You're specifying `CREATE_UNICODE_ENVIRONMENT` but passing an ANSI environment block. Note that it is easier to pass `NULL` for `lpEnvironment` and let the system copy the environment block for you. You should still specify `CREATE_UNICODE_ENVIRONMENT` in this case, as described in the documentation, because your environment block might contain Unicode characters.
Similarly, if you're calling CreateProcessA you should be using STARTUPINFOA.
You don't zero-terminate `tBuf` each time around the loop, so you'll get spurious extra characters in your output buffer.
You need to close `stdoutWriteHandle` before you enter your read loop, or you won't know when the subprocess exits. (Or you could use asynchronous IO and check for process exit explicitly.)
`GetLastError()` is undefined if an API function succeeds, so you should only be calling it if `ReadFile` returns `FALSE`. (Of course, in this case this is purely cosmetic since you aren't acting on the error code.)
For reference, here is my corrected version of your code. I've turned it into plain C (sorry!) because that's what I'm familiar with. I compiled and tested in Unicode mode, but I think it should work without modification in ANSI mode too.
#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;
}
Problem
I have written a function that attempts to read a child process's command line output via a pipe. This should be a simple subset of the MSDN Creating a Child Process with Redirected Input and Output article, but I am clearly making an error of some sort. The ReadFile(...) call below blocks forever no matter if I place it before or after the WaitForSingleObject(...) call that should signal the end of the child process. I have read all the answers that suggest "Use asynchronous ReadFile" and I am open to that suggestion if someone could give me some idea how that is accomplished on a pipe. Although I don't understand why asynchronous I/O should be needed for this case. ``` #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() ```