2016-01-12 4 views
3

Я пытаюсь использовать отдельный процесс для выполнения некоторой работы. В версии Windows я запускаю его с помощью CreateProcess и перенаправляю stdin/stdout обычно (унаследованные анонимные каналы). Трубы работают; однако, когда дочерний процесс завершается, родитель зависает при чтении.Окно CreateProcess exit не обнаружено

Как родитель узнает о завершении дочернего процесса? Я думал, что ReadFile должен был немедленно вернуть ошибку разбитого канала, когда ребенок выйдет.

Код примера для создания дочернего процесса с перенаправленным вводом и выводом имеет ту же проблему, поэтому я отправлю его как MCVE. Она также доступна на https://msdn.microsoft.com/library/windows/desktop/ms682499

#include <windows.h> 
#include <tchar.h> 
#include <stdio.h> 
#include <strsafe.h> 

#define BUFSIZE 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; 

HANDLE g_hInputFile = NULL; 

void CreateChildProcess(void); 
void WriteToPipe(void); 
void ReadFromPipe(void); 
void ErrorExit(PTSTR); 

int _tmain(int argc, TCHAR *argv[]) 
{ 
    SECURITY_ATTRIBUTES saAttr; 

    printf("\n->Start of parent execution.\n"); 

// 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. 

    if (! CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0)) 
     ErrorExit(TEXT("StdoutRd CreatePipe")); 

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

    if (! SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) 
     ErrorExit(TEXT("Stdout SetHandleInformation")); 

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

    if (! CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0)) 
     ErrorExit(TEXT("Stdin CreatePipe")); 

// Ensure the write handle to the pipe for STDIN is not inherited. 

    if (! SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0)) 
     ErrorExit(TEXT("Stdin SetHandleInformation")); 

// Create the child process. 

    CreateChildProcess(); 

// Get a handle to an input file for the parent. 
// This example assumes a plain text file and uses string output to verify data flow. 

    if (argc == 1) 
     ErrorExit(TEXT("Please specify an input file.\n")); 

    g_hInputFile = CreateFile(
     argv[1], 
     GENERIC_READ, 
     0, 
     NULL, 
     OPEN_EXISTING, 
     FILE_ATTRIBUTE_READONLY, 
     NULL); 

    if (g_hInputFile == INVALID_HANDLE_VALUE) 
     ErrorExit(TEXT("CreateFile")); 

// Write to the pipe that is the standard input for a child process. 
// Data is written to the pipe's buffers, so it is not necessary to wait 
// until the child process is running before writing data. 

    WriteToPipe(); 
    printf("\n->Contents of %s written to child STDIN pipe.\n", argv[1]); 

// Read from pipe that is the standard output for child process. 

    printf("\n->Contents of child process STDOUT:\n\n", argv[1]); 
    ReadFromPipe(); 

    printf("\n->End of parent execution.\n"); 

// The remaining open handles are cleaned up when this process terminates. 
// To avoid resource leaks in a larger application, close handles explicitly. 

    return 0; 
} 

void CreateChildProcess() 
// Create a child process that uses the previously created pipes for STDIN and STDOUT. 
{ 
    TCHAR szCmdline[]=TEXT("child"); 
    PROCESS_INFORMATION piProcInfo; 
    STARTUPINFO siStartInfo; 
    BOOL bSuccess = FALSE; 

// Set up members of the PROCESS_INFORMATION structure. 

    ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); 

// 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.hStdError = g_hChildStd_OUT_Wr; 
    siStartInfo.hStdOutput = g_hChildStd_OUT_Wr; 
    siStartInfo.hStdInput = g_hChildStd_IN_Rd; 
    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 

    // If an error occurs, exit the application. 
    if (! bSuccess) 
     ErrorExit(TEXT("CreateProcess")); 
    else 
    { 
     // Close handles to the child process and its primary thread. 
     // Some applications might keep these handles to monitor the status 
     // of the child process, for example. 

     CloseHandle(piProcInfo.hProcess); 
     CloseHandle(piProcInfo.hThread); 
    } 
} 

void WriteToPipe(void) 

// Read from a file and write its contents to the pipe for the child's STDIN. 
// Stop when there is no more data. 
{ 
    DWORD dwRead, dwWritten; 
    CHAR chBuf[BUFSIZE]; 
    BOOL bSuccess = FALSE; 

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

     bSuccess = WriteFile(g_hChildStd_IN_Wr, chBuf, dwRead, &dwWritten, NULL); 
     if (! bSuccess) break; 
    } 

// Close the pipe handle so the child process stops reading. 

    if (! CloseHandle(g_hChildStd_IN_Wr)) 
     ErrorExit(TEXT("StdInWr CloseHandle")); 
} 

void ReadFromPipe(void) 

// Read output from the child process's pipe for STDOUT 
// and write to the parent process's pipe for STDOUT. 
// Stop when there is no more data. 
{ 
    DWORD dwRead, dwWritten; 
    CHAR chBuf[BUFSIZE]; 
    BOOL bSuccess = FALSE; 
    HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE); 

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

     bSuccess = WriteFile(hParentStdOut, chBuf, 
          dwRead, &dwWritten, NULL); 
     if (! bSuccess) break; 
    } 
} 

void ErrorExit(PTSTR lpszFunction) 

// Format a readable error message, display a message box, 
// and exit from the application. 
{ 
    LPVOID lpMsgBuf; 
    LPVOID lpDisplayBuf; 
    DWORD dw = GetLastError(); 

    FormatMessage(
     FORMAT_MESSAGE_ALLOCATE_BUFFER | 
     FORMAT_MESSAGE_FROM_SYSTEM | 
     FORMAT_MESSAGE_IGNORE_INSERTS, 
     NULL, 
     dw, 
     MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
     (LPTSTR) &lpMsgBuf, 
     0, NULL); 

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 
     (lstrlen((LPCTSTR)lpMsgBuf)+lstrlen((LPCTSTR)lpszFunction)+40)*sizeof(TCHAR)); 
    StringCchPrintf((LPTSTR)lpDisplayBuf, 
     LocalSize(lpDisplayBuf)/sizeof(TCHAR), 
     TEXT("%s failed with error %d: %s"), 
     lpszFunction, dw, lpMsgBuf); 
    MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK); 

    LocalFree(lpMsgBuf); 
    LocalFree(lpDisplayBuf); 
    ExitProcess(1); 
} 

Вот код дочернего процесса:

#include <windows.h> 
#include <stdio.h> 

#define BUFSIZE 4096 

int main(void) 
{ 
    CHAR chBuf[BUFSIZE]; 
    DWORD dwRead, dwWritten; 
    HANDLE hStdin, hStdout; 
    BOOL bSuccess; 

    hStdout = GetStdHandle(STD_OUTPUT_HANDLE); 
    hStdin = GetStdHandle(STD_INPUT_HANDLE); 
    if ( 
     (hStdout == INVALID_HANDLE_VALUE) || 
     (hStdin == INVALID_HANDLE_VALUE) 
    ) 
     ExitProcess(1); 

    // Send something to this process's stdout using printf. 
    printf("\n ** This is a message from the child process. ** \n"); 

    // This simple algorithm uses the existence of the pipes to control execution. 
    // It relies on the pipe buffers to ensure that no data is lost. 
    // Larger applications would use more advanced process control. 

    for (;;) 
    { 
    // Read from standard input and stop on error or no data. 
     bSuccess = ReadFile(hStdin, chBuf, BUFSIZE, &dwRead, NULL); 

     if (! bSuccess || dwRead == 0) 
     break; 

    // Write to standard output and stop on error. 
     bSuccess = WriteFile(hStdout, chBuf, dwRead, &dwWritten, NULL); 

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

Вот результат от выполнения. Проблема заключается в том, что родитель никогда не обнаруживает выход ребенка, поэтому никогда не заканчивается.

->Start of parent execution. 

->Contents of E:\Development\test\redirect\Debug/data.txt written to child STDIN 
pipe. 

->Contents of child process STDOUT: 

+ This is a test of windows pipes. 
+ This is only a test. 
** This is a message from the child process. ** 
+0

Можете ли вы отредактировать сообщение, чтобы сделать небольшой пример кода, чтобы показать, как вы совершили звонки? – ryyker

+0

он висит i * на 'ReadFromPipe', что очевидно, используйте потоки для чтения и записи в каналы. – milevyo

+1

Я не считаю, что проблемы (или их отсутствие) являются проблемой. Я использовал этот же код или очень похожи, как базу для приложения, где я запускаю процесс, а затем возвращаю. Оно работает. – ryyker

ответ

2
Я думал, что ReadFile должен был немедленно вернуть ошибку сломанной трубы, когда ребенок выйдет.

Не совсем. Он возвращается немедленно, когда другой конец трубы закрыт, что происходит, как только последний дескриптор закрыт.

Родительский процесс не закрывает свою копию дескриптора. Что касается Windows, другой поток в родительском процессе может планировать писать в этот дескриптор, поэтому ReadFile продолжает ждать, пока это произойдет.

Таким образом, после создания дочернего процесса, закройте ручку:

CloseHandle(g_hChildStd_OUT_Wr); 

PS: вы можете явно проверить, был ли процесс вышел на ожидание процесса ручки. Но использование трубы обычно проще и эффективнее.

+0

Это все, спасибо. –

Смежные вопросы