2016-03-13 314 views
0

如何從CMD.EXE使用的CreateProcess()和CreatePipe()讀取輸出讀取輸出如何從CMD.EXE使用的CreateProcess()和CreatePipe()

我一直在試圖建立一個子進程使用指定/K dir的命令行執行cmd.exe。目的是使用管道將命令的輸出讀回父進程。

我已經有CreateProcess()工作,但涉及管道的步驟造成我的麻煩。使用管道,新控制檯窗口不顯示(像以前一樣),並且父進程停留在對ReadFile()的調用中。

有沒有人有我做錯了什麼想法?

#include <Windows.h> 
#include <stdio.h> 
#include <tchar.h> 

#define BUFFSZ 4096 

HANDLE g_hChildStd_IN_Rd = NULL; 
HANDLE g_hChildStd_IN_Wr = NULL; 
HANDLE g_hChildStd_OUT_Rd = NULL; 
HANDLE g_hChildStd_OUT_Wr = NULL; 

int wmain(int argc, wchar_t* argv[]) 
{ 
    int result; 
    wchar_t aCmd[BUFFSZ] = TEXT("/K dir"); // CMD /? 
    STARTUPINFO si; 
    PROCESS_INFORMATION pi; 
    SECURITY_ATTRIBUTES sa; 

    printf("Starting...\n"); 

    ZeroMemory(&si, sizeof(STARTUPINFO)); 
    ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); 
    ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES)); 

    // Create one-way pipe for child process STDOUT 
    if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &sa, 0)) { 
     printf("CreatePipe() error: %ld\n", GetLastError()); 
    } 

    // Ensure read handle to pipe for STDOUT is not inherited 
    if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) { 
     printf("SetHandleInformation() error: %ld\n", GetLastError()); 
    } 

    // Create one-way pipe for child process STDIN 
    if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &sa, 0)) { 
     printf("CreatePipe() error: %ld\n", GetLastError()); 
    } 

    // Ensure write handle to pipe for STDIN is not inherited 
    if (!SetHandleInformation(g_hChildStd_IN_Rd, HANDLE_FLAG_INHERIT, 0)) { 
     printf("SetHandleInformation() error: %ld\n", GetLastError()); 
    } 

    si.cb = sizeof(STARTUPINFO); 
    si.hStdError = g_hChildStd_OUT_Wr; 
    si.hStdOutput = g_hChildStd_OUT_Wr; 
    si.hStdInput = g_hChildStd_IN_Rd; 
    si.dwFlags |= STARTF_USESTDHANDLES; 

    sa.nLength = sizeof(SECURITY_ATTRIBUTES); 
    sa.lpSecurityDescriptor = NULL; 
    // Pipe handles are inherited 
    sa.bInheritHandle = true; 

    // Creates a child process 
    result = CreateProcess(
     TEXT("C:\\Windows\\System32\\cmd.exe"),  // Module 
     aCmd,          // Command-line 
     NULL,          // Process security attributes 
     NULL,          // Primary thread security attributes 
     true,          // Handles are inherited 
     CREATE_NEW_CONSOLE,       // Creation flags 
     NULL,          // Environment (use parent) 
     NULL,          // Current directory (use parent) 
     &si,          // STARTUPINFO pointer 
     &pi           // PROCESS_INFORMATION pointer 
     ); 

    if (result) { 
     printf("Child process has been created...\n"); 
    } 
    else { 
     printf("Child process could not be created\n"); 
    } 

    bool bStatus; 
    CHAR aBuf[BUFFSZ + 1]; 
    DWORD dwRead; 
    DWORD dwWrite; 
    // GetStdHandle(STD_OUTPUT_HANDLE) 

    while (true) { 
     bStatus = ReadFile(g_hChildStd_OUT_Rd, aBuf, sizeof(aBuf), &dwRead, NULL); 
     if (!bStatus || dwRead == 0) { 
      break; 
     } 
     aBuf[dwRead] = '\0'; 
     printf("%s\n", aBuf); 
    } 

     // Wait until child process exits 
     WaitForSingleObject(pi.hProcess, INFINITE); 

     // Close process and thread handles 
     CloseHandle(pi.hProcess); 
     CloseHandle(pi.hThread); 

     printf("Stopping...\n"); 

     return 0; 
    } 
+0

代碼只做了一半,死鎖是不可避免的。直到進程填充它的stdout緩衝區,ReadFile()纔會完成,因此需要刷新或關閉句柄。緩衝區不會被填充,因爲它只包含提示。沒有更多的東西被添加,也不會關閉句柄,因爲你沒有告訴它做任何事情。不要讀stderr也是一個可能導致死鎖的bug,否則通過告訴它合併stdout和stderr輸出很容易避免。那麼,這就是爲什麼,修復它並不是很有用。 –

+0

我該怎麼做?我應該創建另一個線程?我該如何沖洗? – Shuzheng

+1

你不能強制它刷新,它是cmd.exe的stdout緩衝區。除非你發送「退出\ r」給標準輸入。該程序設計爲在使用/ k時交互使用,重定向只在使用/ c時纔有效。修復這個需要使用重疊I/O,因此您可以同時讀取stderr和stdout,並使用WaitForMultipleObjects(),以便您可以等待所有三個句柄。 –

回答

0

我想你做的一切都沒錯。但是,cmd.exe在啓動和您的ReadFile塊之後不打印任何數據或打印非常少量的數據。如果你將你的循環移動到後臺線程並運行其他循環,它將讀取你的輸入並將其發送到cmd.exe,我認爲你可以看到任何效果。 您可以使讀取緩衝區更小(例如16個字節)。

0

下面是一個線程的示例(取自一個較大的程序),它可以完成您正在尋找的任務。它爲它創建的過程創建stdout和stderr的管道,然後進入讀取這些管道的循環,直到程序結束。

DWORD WINAPI ThreadProc(LPVOID lpParameter) 
    { 
#define EVENT_NAME "Global\\RunnerEvt" 

    HANDLE hev; 
    SECURITY_ATTRIBUTES psa; 
    InitSAPtr(&psa); 
    DWORD waitRc; 
    DWORD bytesRead; 
    int manual_triggered = 1; 

    hev = CreateEvent(&psa, FALSE, FALSE, EVENT_NAME); 

    // Create pipes we'll read 

     for(;;) 
     { 

     if (manual_triggered) 
     { 
     waitRc = WAIT_OBJECT_0; 
     manual_triggered = 0; 
     } 
     else 
     { 
     waitRc = WaitForSingleObject(hev, 500); 
     } 

     if (waitRc == WAIT_OBJECT_0) 
     { 
     `logprint`f(LOG_DBG, "Received command to run process\n"); 

     CreateChildOutFile(); 

     stdOutEvt = CreateEvent(&psa, TRUE, FALSE, 0); 
     stdOutOvl.hEvent = stdOutEvt; 

     stdErrEvt = CreateEvent(&psa, TRUE, FALSE, 0); 
     stdErrOvl.hEvent = stdErrEvt; 

     gStdOutReadHand = CreateNamedPipe(STD_OUT_PIPE_NAME, PIPE_ACCESS_DUPLEX + FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE + PIPE_READMODE_BYTE, 
      PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, &psa); 
     if (gStdOutReadHand == INVALID_HANDLE_VALUE) 
      { 
      log(LOG_DBG, "Error %d on create STDOUT pipe\n", GetLastError()); 
      } 

     gStdErrReadHand = CreateNamedPipe(STD_ERR_PIPE_NAME, PIPE_ACCESS_DUPLEX + FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE + PIPE_READMODE_BYTE, 
      PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, &psa); 
     if (gStdErrReadHand == INVALID_HANDLE_VALUE) 
      { 
      log(LOG_DBG, "Error %d on create STDERR pipe\n", GetLastError()); 
      } 

     runProcess(); 

     log(LOG_DBG, "After runProcess, new PID is %d/%x\n", piProcInfo.dwProcessId, piProcInfo.dwProcessId); 

     if (piProcInfo.dwProcessId == 0) 
      { 
      log(LOG_DBG, "runProcess failed, closing child STDIN/STDERR\n"); 
      closeChildPipes(); 

#define FAIL_MSG "Child process failed to start\n" 
      writeChildOutFile(FAIL_MSG, strlen(FAIL_MSG)); 

      CloseHandle(hChildOut); 
      } 
     else 
      { 
      log(LOG_DBG, "Child process created, setting up for redir/restart/termination\n"); 

      issueRead(gStdOutReadHand, &stdOutOvl, stdOutBuff, &stdOutBytesAvail); 
      //log(LOG_DBG, "After read set on STDOUT\n"); 

      issueRead(gStdErrReadHand, &stdErrOvl, stdErrBuff, &stdErrBytesAvail); 
      //log(LOG_DBG, "After read set on STDERR\n"); 

      HANDLE harr[4]; 

      for(;;) 
       { 
       harr[0] = hev; 
       harr[1] = piProcInfo.hProcess; 
       harr[2] = stdOutEvt; 
       harr[3] = stdErrEvt; 

       DWORD waitRc2 = WaitForMultipleObjects(4, harr, FALSE, 500); 

       #if 0 
       if (waitRc2 == -1) 
        { 
        log(LOG_DBG, "Wait error %d\n", GetLastError()); 
        Sleep(500); 
        } 

       log(LOG_DBG, "waitRc2 %d\n", waitRc2); 
       #endif 


       if ((waitRc2 - WAIT_OBJECT_0) == 0) 
        { 
        log(LOG_DBG, "Woke up because another trigger command was received\n"); 
        #define NEW_CMD_MSG "Child process is being terminated because new trigger received\n" 

        writeChildOutFile(NEW_CMD_MSG, strlen(NEW_CMD_MSG)); 

        terminateChild(); 
        CloseHandle(hChildOut); 
        manual_triggered = 1; 
        break; 
        } 
       else if ((waitRc2 - WAIT_OBJECT_0) == 1) 
        { 
        //log(LOG_DBG, "Woke up because child has terminated\n"); 
        closeChildPipes(); 
        #define NORM_MSG "Normal child process termination\n" 
        writeChildOutFile(NORM_MSG, strlen(NORM_MSG)); 
        CloseHandle(hChildOut); 
        break; 
        } 
       else if ((waitRc2 - WAIT_OBJECT_0) == 2) 
        { 
        //log(LOG_DBG, "Woke up because child has stdout\n"); 
        if (GetOverlappedResult(gStdOutReadHand, &stdOutOvl, &bytesRead, TRUE)) 
        { 
        writeChildOutFile(stdOutBuff, bytesRead); 
        ResetEvent(stdOutEvt); 
        issueRead(gStdOutReadHand, &stdOutOvl, stdOutBuff, &stdOutBytesAvail); 
        } 

        } 
       else if ((waitRc2 - WAIT_OBJECT_0) == 3) 
        { 
        //log(LOG_DBG, "Woke up because child has stderr\n"); 

        if (GetOverlappedResult(gStdErrReadHand, &stdErrOvl, &bytesRead, TRUE)) 
        { 
        writeChildOutFile(stdErrBuff, bytesRead); 
        ResetEvent(stdErrEvt); 
        issueRead(gStdErrReadHand, &stdErrOvl, stdErrBuff, &stdErrBytesAvail); 
        } 
        } 
       else 
        { 
        if (gShuttingDown) 
        { 
        log(LOG_DBG, "Woke with active child and service is terminating\n"); 

#define SHUTDOWN_MSG "Child process is being terminated because the service is shutting down\n" 

        writeChildOutFile(SHUTDOWN_MSG, strlen(SHUTDOWN_MSG)); 
        terminateChild(); 
        CloseHandle(hChildOut); 
        break; 
        } 
        } 

       if (gShuttingDown) 
        { 
        break; 
        } 

       } 
      } 
     } 
     else if (gShuttingDown) 
     { 
     break; 
     } 

     CloseHandle(gStdOutReadHand); 
     CloseHandle(gStdErrReadHand); 

     } 

    return 0; 
    } 

void writeChildOutFile(char *msg, int len) 
    { 
    DWORD bytesWritten; 
    WriteFile(hChildOut, msg, len, &bytesWritten, 0); 
    } 


void terminateChild(void) 
    { 
    if (piProcInfo.dwProcessId != 0) 
     { 
     TerminateProcess(piProcInfo.hProcess, -1); 
     CloseHandle(piProcInfo.hThread); 
     CloseHandle(piProcInfo.hProcess); 
     closeChildPipes(); 
     } 
    } 

void closeChildPipes(void) 
    { 
    CloseHandle(g_hChildStd_OUT_Wr); 
    CloseHandle(g_hChildStd_ERR_Wr); 
    } 

void runProcess(void) 
    { 
    SECURITY_ATTRIBUTES saAttr; 

    // Set the bInheritHandle flag so pipe handles are inherited. 
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
    saAttr.bInheritHandle = TRUE; 
    saAttr.lpSecurityDescriptor = NULL; 

    // Create a pipe for the child process's STDOUT. 
    TCHAR szCmdline[]=TEXT("cmd.exe /C C:\\temp\\RunnerService.bat"); 
    STARTUPINFO siStartInfo; 
    BOOL bSuccess = FALSE; 

// Set up members of the PROCESS_INFORMATION structure. 

    ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); 

    g_hChildStd_OUT_Wr = CreateFile (STD_OUT_PIPE_NAME, 
       FILE_WRITE_DATA, 
       0, 
       &saAttr, 
       OPEN_EXISTING, 
       0, 
       NULL); 

    if (g_hChildStd_OUT_Wr == INVALID_HANDLE_VALUE) 
     { 
     log(LOG_DBG, "Error creating child proc stdout file %d\n", GetLastError()); 
     } 


    g_hChildStd_ERR_Wr = CreateFile (STD_ERR_PIPE_NAME, 
       FILE_WRITE_DATA, 
       0, 
       &saAttr, 
       OPEN_EXISTING, 
       0, 
       NULL); 

    if (g_hChildStd_ERR_Wr == INVALID_HANDLE_VALUE) 
     { 
     log(LOG_DBG, "Error creating child proc stderr file %d\n", GetLastError()); 
     } 


// Set up members of the STARTUPINFO structure. 
// This structure specifies the STDIN and STDOUT handles for redirection. 

    ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); 
    siStartInfo.cb = sizeof(STARTUPINFO); 
    siStartInfo.hStdOutput = g_hChildStd_OUT_Wr; 
    siStartInfo.hStdError = g_hChildStd_ERR_Wr; 
    siStartInfo.dwFlags |= STARTF_USESTDHANDLES; 

// Create the child process. 

    bSuccess = CreateProcess(NULL, 
     szCmdline,  // command line 
     NULL,   // process security attributes 
     NULL,   // primary thread security attributes 
     TRUE,   // handles are inherited 
     0,    // creation flags 
     NULL,   // use parent's environment 
     NULL,   // use parent's current directory 
     &siStartInfo, // STARTUPINFO pointer 
     &piProcInfo); // receives PROCESS_INFORMATION 

    } 


void CreateChildOutFile(void) 
    { 
    SYSTEMTIME st; 
    SECURITY_ATTRIBUTES sa; 
    char fName[_MAX_PATH]; 

    InitSAPtr(&sa); 

    GetLocalTime(&st); 

    sprintf(fName, "C:\\TEMP\\runsvcchild_%02d_%02d_%02d_%04d.out", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); 

    hChildOut = CreateFile(fName, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); 
    } 

void issueRead(HANDLE hFile, OVERLAPPED *overLapped, char *buf, DWORD *dwRead) 
    { 
    //log(LOG_DBG, "Start of issueRead, hfile %08x, ovl is %08x\n", hFile, overLapped); 
    BOOL brc = ReadFile(hFile, buf, 4096, dwRead, overLapped); 
    if (!brc) 
     { 
     DWORD dwle = GetLastError(); 
     if (dwle != ERROR_IO_PENDING) 
     { 
     log(LOG_DBG, "Error %d on ReadFile\n", dwle); 
     } 
     } 
    else 
     { 
     // log(LOG_DBG, "Read issued\n"); 
     } 
    } 
1

微妙的出路您的問題是,以確保您關閉了管道的兩端不需要:

Us           Child 
+------------------+      +---------------+ 
|     |      |    | 
|   g_hChildStd_IN_Wr----->g_hChildStd_IN_Rd   | 
|     |      |    | 
|  g_hChildStd_OUT_Rd<------g_hChildStd_OUT_Wr   | 
|     |      |    | 
+------------------+      +---------------+ 

你的父進程只需要每根管子的一端:

  • 子輸入管的可寫端
  • 子輸出管的端可讀

啓動子進程後:確保關閉不再需要的管道的末端。

result = CreateProcess(...); 

//CreateProcess demands that we close these two populated handles when we're done with them. We're done with them. 
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 console app. 
    When the app closes, it will close the pipe, and ReadFile will return 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; 

常見問題與大多數解決方案是,人們試圖等待進程句柄。這有很多問題;主要的一點是如果你等待孩子終止,孩子將永遠無法終止。

如果孩子正試圖通過管道向您發送輸出,並且您正在等待,您不會清空管道的末端。最終管道變滿。當孩子試圖寫滿管道時,WriteFile呼叫會等待管道留出空間。因此,子進程將從永遠不會終止;你已經僵化了一切。

正確的解決方案是通過簡單地從管道讀取來。一旦子進程終止,它會CloseHandle它的管道末端。下次您嘗試從管道讀取時,您會被告知管道已關閉(ERROR_BROKEN_PIPE)。這就是你如何知道這個過程已經完成,你沒有更多的東西可讀;所有這些都沒有危險的MsgWaitForSingleObject,它們很容易出錯,並且會導致你想要避免的錯誤。

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);