How to read output from cmd.exe using CreateProcess() and CreatePipe()

后端 未结 6 1817
盖世英雄少女心
盖世英雄少女心 2020-12-09 12:02

How to read output from cmd.exe using CreateProcess() and CreatePipe()

I have been trying to create a child process executing c

6条回答
  •  Happy的楠姐
    2020-12-09 12:19

    The subtle way out of your problem is to make sure you close the ends of the pipe you don't need. Your parent process has four handles:

    • two of them are your ends of the pipe
    • two of them are the child's end of the pipe

     

    ╔══════════════════╗                ╔══════════════════╗
    ║  Parent Process  ║                ║  Child Process   ║
    ╠══════════════════╣                ╠══════════════════╣
    ║                  ║                ║                  ║
    ║ g_hChildStd_IN_Wr╟───────────────>║g_hChildStd_IN_Rd ║ 
    ║                  ║                ║                  ║ 
    ║g_hChildStd_OUT_Rd║<───────────────╢g_hChildStd_OUT_Wr║
    ║                  ║                ║                  ║
    ╚══════════════════╝                ╚══════════════════╝
    

    Your parent process only needs one end of each pipe:

    • writable end of the child input pipe
    • readable end of the child output pipe

    Once you've launched your child process: be sure to close those ends of the pipe you no longer need:

    • CloseHandle(g_hChildStd_IN_Rd)
    • CloseHandle(g_hChildStd_OUT_Wr)

    Leaving:

    ╔══════════════════╗                ╔══════════════════╗
    ║  Parent Process  ║                ║  Child Process   ║
    ╠══════════════════╣                ╠══════════════════╣
    ║                  ║                ║                  ║
    ║ g_hChildStd_IN_Wr╟───────────────>║                  ║ 
    ║                  ║                ║                  ║ 
    ║g_hChildStd_OUT_Rd║<───────────────╢                  ║
    ║                  ║                ║                  ║
    ╚══════════════════╝                ╚══════════════════╝
    

    Or more fully:

    STARTUP_INFO si;
    PROCESS_INFO pi;
    result = CreateProcess(..., ref si, ref pi);
    
    //A common bug among a lot of programmers: they don't realize they
    //are required to call CloseHandle on the two handles placed in PROCESS_INFO.
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    
    /*
       We've given the console app the writable end of the pipe during CreateProcess; we don't need it anymore.
       We do keep the handle for the *readable* end of the pipe; as we still need to read from it.
       The other reason to close the writable-end handle now is so that there's only one out-standing reference to the writeable end: held by the child process.
       When the child processes closes, it will close the pipe, and 
       your call to ReadFile will fail with error code:
          109 (The pipe has been ended).
    
       That's how we'll know the console app is done. (no need to wait on process handles with buggy infinite waits)
    */
    CloseHandle(g_hChildStd_OUT_Wr);
    g_hChildStd_OUT_Wr = 0;
    CloseHandle(g_hChildStd_IN_Rd);
    g_hChildStd_OUT_Wr = 0;
    

    The Ignorant Hacks - Waiting on the child process

    The common problem with most solutions is that people try to wait on a process handle.

    • they create event objects
    • they try to MsgWait for events to be signaled
    • they try to MsgWait for child processes to end

    That's wrong. That's all wrong.

    There are many problems with these ideas; the main one being:

    • if you try to wait for the child the terminate
    • the child will never be able to terminate

    If the child is trying to send you output through the pipe, and you're INFINITE waiting, you're not emptying your end of the pipe. Eventually the pipe the child is writing to becomes full. When the child tries to write to a pipe that is full, its WriteFile call waits for the pipe to have some room. As a result the child process will never terminate; you've deadlocked everything.

    The correct solution comes by simply reading from the pipe.

    • Once the child process terminates,
    • it will CloseHandle on its end of the pipes.
    • The next time you try to read from the pipe
    • you'll be told the pipe has been closed (ERROR_BROKEN_PIPE).
    • That's how you know the process is done and you have no more stuff to read.

    All without a dangerous MsgWaitForSingleObject, that are error-prone to use correctly, and causes the very bug you want to avoid.

    String outputText = "";
    
    //Read will return when the buffer is full, or if the pipe on the other end has been broken
    while (ReadFile(stdOutRead, aBuf, Length(aBuf), &bytesRead, null)
       outputText = outputText + Copy(aBuf, 1, bytesRead);
    
    //ReadFile will either tell us that the pipe has closed, or give us an error
    DWORD le = GetLastError;
    
    //And finally cleanup
    CloseHandle(g_hChildStd_IN_Wr);
    CloseHandle(g_hChildStd_OUT_Rd);
    
    if (le != ERROR_BROKEN_PIPE) //"The pipe has been ended."
       RaiseLastOSError(le);
    

提交回复
热议问题