rwallace
rwallace

Reputation: 33385

CreateProcess and capture stdout

When using CreateProcess to run another program, what is the recommended way to capture the stdout? That is, to take whatever the second program was printing to stdout, and end up with it in an array where the first program can analyze it?

Both programs are straight Win32 programs written in C, no fancy stuff.

Upvotes: 5

Views: 21619

Answers (3)

domusmagister
domusmagister

Reputation: 167

Microsoft example consider both program written from the same developer. In my case I was launching a generic program which output on stdout/stderr. I changed the Microsoft example in this way. The following function run a generic externalProgram with command line arguments:

HANDLE m_hChildStd_OUT_Rd = NULL;
HANDLE m_hChildStd_OUT_Wr = NULL;
HANDLE m_hreadDataFromExtProgram = NULL;

HRESULT RunExternalProgram(std::string externalProgram, std::string arguments)
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    SECURITY_ATTRIBUTES saAttr;

    ZeroMemory(&saAttr, sizeof(saAttr));
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;
    saAttr.lpSecurityDescriptor = NULL;

    // Create a pipe for the child process's STDOUT. 

    if (!CreatePipe(&m_hChildStd_OUT_Rd, &m_hChildStd_OUT_Wr, &saAttr, 0))
    {
        // log error
        return HRESULT_FROM_WIN32(GetLastError());
    }

    // Ensure the read handle to the pipe for STDOUT is not inherited.

    if (!SetHandleInformation(m_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
    {
        // log error
        return HRESULT_FROM_WIN32(GetLastError());
    }

    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.hStdError = m_hChildStd_OUT_Wr;
    si.hStdOutput = m_hChildStd_OUT_Wr;
    si.dwFlags |= STARTF_USESTDHANDLES;

    ZeroMemory(&pi, sizeof(pi));

    std::string commandLine = extProgram + " " + arguments;

    // Start the child process. 
    if (!CreateProcessA(NULL,           // No module name (use command line)
        (TCHAR*)commandLine.c_str(),    // Command line
        NULL,                           // Process handle not inheritable
        NULL,                           // Thread handle not inheritable
        TRUE,                           // Set handle inheritance
        0,                              // No creation flags
        NULL,                           // Use parent's environment block
        NULL,                           // Use parent's starting directory 
        &si,                            // Pointer to STARTUPINFO structure
        &pi)                            // Pointer to PROCESS_INFORMATION structure
        )
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }
    else
    {
        m_hreadDataFromExtProgram  = CreateThread(0, 0, readDataFromExtProgram, NULL, 0, NULL);
    }
    return S_OK;
}

If CreateProcessA succedeed, an external thread will be launched to asynchronousvly capture the output from the launched program. The code executed from the external thread is the following one:

DWORD __stdcall readDataFromExtProgram(void * argh)
{
    DWORD dwRead;
    CHAR chBuf[BUFSIZE];
    BOOL bSuccess = FALSE;

    for (;;)
    {
        bSuccess = ReadFile(m_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
        if (!bSuccess || dwRead == 0) continue;

        // Log chBuf

        if (!bSuccess) break;
    }
    return 0;
}

I omitted any closing condition on all HANDLE and threads involved in the code.

Upvotes: 10

stefan.gal
stefan.gal

Reputation: 312

The MSDN sample mentioned by Remy Lebeau is not working for me, it hangs forever in the WriteToPipe() call. To make it simpler I replaced the child process with "ping 127.0.0.1" and commented out the WriteToPipe() call. Now I am able to capture the output.

Upvotes: 2

Anders
Anders

Reputation: 101616

The short answer is to create an anonymous pipe, setting the hStdOut/hStdErr and dwFlag members of the STARTUPINFO structure accordingly, and have CreateProcess() inherit the handle for the writing end of the pipe. Don't forget to close your writing handle of your pipe, then you can read from the reading handle of the pipe in a loop until it fails with an ERROR_BROKEN_PIPE error.

MSDN provides a detailed example of this:

Creating a Child Process with Redirected Input and Output

You are not the first person to do this, there should be plenty of example code and duplicate questions here on StackOverflow.

Upvotes: 11

Related Questions