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() ```

Original source