子进程(通过CreateProcess)通过重定向的stdout和stdin停滞在getch()上

时间:2018-08-11 19:33:11

标签: c windows winapi visual-studio-2015 createprocess

我试图用CreateProcess()启动一个进程,将stdin和stdout重定向到管道。当子进程仅由printf()语句组成时,我看到它们通过管道传递给父进程并显示得很好。如果我的子进程执行了printf()和_getch()语句,那么事情将失败。我已经以多种方式考虑了一种可能的deadlock between the pipes无效:

  • 改变事物的顺序,
  • 应用PeekNamedPipe()和Sleep()语句,以及
  • 使用命名管道重叠I / O。

我只是怀疑某个地方存在细微的配置问题。这是较大程序中一个问题的一部分,但我已将其简化为这个简单的测试用例。我从"Creating a Child Process with Redirected Input and Output"的Microsoft示例开始。那行得通,所以也许使用ReadFile()的子进程行得通,但是我的问题是_getch()(在其他程序中似乎有相关的失败)。我用测试程序替换了子进程,但该进程停止了。我尝试如上所述解决死锁,并为此this example for using named pipes之后实现了重叠的I / O(在我的阅读中,有人提到Windows的命名管道和匿名管道实现是合理统一的)。

同样,如果子级仅发出printfs但以_getch()失败,则可以正常工作。值得注意的是,如果子程序中存在_getch(),则即使printfs也不会出现-甚至是在_getch()之前发出的printfs()。我已经读过管道有缓冲,并且如上所述,它们在管道的另一端有潜在的死锁,但是除了下面要做的事情之外,我想不出其他方法可以避免这种情况。

以防万一,我还确保了command-line buffer since CreateProcess() is known to modify it的堆缓冲区很大。

这是我的父级测试代码,其中第一个布尔配置重叠/不重叠行为:

#include <string>
#include <Windows.h>
#include <tchar.h>
#include <stdio.h> 
#include <strsafe.h>
#include <conio.h>
#include <assert.h>

TCHAR szCmdline[] = TEXT("child.exe");
bool OverlappedStdOutRd = true;
bool OverlappedStdInWr = true;

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

using namespace std;

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

static ULONG PipeSerialNumber = 1;
static BOOL APIENTRY MyCreatePipeEx(
    OUT LPHANDLE lpReadPipe,
    OUT LPHANDLE lpWritePipe,
    IN LPSECURITY_ATTRIBUTES lpPipeAttributes,
    IN DWORD nSize,
    DWORD dwReadMode,
    DWORD dwWriteMode
)
/*++

Routine Description:

The CreatePipeEx API is used to create an anonymous pipe I/O device.
Unlike CreatePipe FILE_FLAG_OVERLAPPED may be specified for one or
both handles.
Two handles to the device are created.  One handle is opened for
reading and the other is opened for writing.  These handles may be
used in subsequent calls to ReadFile and WriteFile to transmit data
through the pipe.

Arguments:

lpReadPipe - Returns a handle to the read side of the pipe.  Data
may be read from the pipe by specifying this handle value in a
subsequent call to ReadFile.

lpWritePipe - Returns a handle to the write side of the pipe.  Data
may be written to the pipe by specifying this handle value in a
subsequent call to WriteFile.

lpPipeAttributes - An optional parameter that may be used to specify
the attributes of the new pipe.  If the parameter is not
specified, then the pipe is created without a security
descriptor, and the resulting handles are not inherited on
process creation.  Otherwise, the optional security attributes
are used on the pipe, and the inherit handles flag effects both
pipe handles.

nSize - Supplies the requested buffer size for the pipe.  This is
only a suggestion and is used by the operating system to
calculate an appropriate buffering mechanism.  A value of zero
indicates that the system is to choose the default buffering
scheme.

Return Value:

TRUE - The operation was successful.

FALSE/NULL - The operation failed. Extended error status is available
using GetLastError.

--*/
{
    HANDLE ReadPipeHandle, WritePipeHandle;
    DWORD dwError;
    CHAR PipeNameBuffer[MAX_PATH];

    //
    // Only one valid OpenMode flag - FILE_FLAG_OVERLAPPED
    //
    if ((dwReadMode | dwWriteMode) & (~FILE_FLAG_OVERLAPPED)) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    //
    //  Set the default timeout to 120 seconds
    //

    if (nSize == 0) {
        nSize = 4096;
    }

    sprintf_s(PipeNameBuffer,
        "\\\\.\\Pipe\\TruthPipe.%08x.%08x",
        GetCurrentProcessId(),
        PipeSerialNumber++      // TODO: Should use InterlockedIncrement() here to be thread-safe.
    );

    ReadPipeHandle = CreateNamedPipeA(
        PipeNameBuffer,
        PIPE_ACCESS_INBOUND | dwReadMode,
        PIPE_TYPE_BYTE | PIPE_WAIT,
        1,              // Number of pipes
        nSize,          // Out buffer size
        nSize,          // In buffer size
        1000,           // Timeout in ms
        lpPipeAttributes
    );

    if (!ReadPipeHandle) {
        return FALSE;
    }

    WritePipeHandle = CreateFileA(
        PipeNameBuffer,
        GENERIC_WRITE,
        0,                         // No sharing
        lpPipeAttributes,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | dwWriteMode,
        NULL                       // Template file
    );

    if (INVALID_HANDLE_VALUE == WritePipeHandle) {
        dwError = GetLastError();
        CloseHandle(ReadPipeHandle);
        SetLastError(dwError);
        return FALSE;
    }

    *lpReadPipe = ReadPipeHandle;
    *lpWritePipe = WritePipeHandle;
    return(TRUE);
}

bool OutstandingWrite = false;
OVERLAPPED WriteOverlapped;
CHAR chWriteBuf[BUFSIZE];
DWORD dwBytesWritten;
DWORD dwBytesToWrite;

bool OutstandingRead = false;
OVERLAPPED ReadOverlapped;
CHAR chReadBuf[BUFSIZE];
DWORD dwBytesRead;

void OnReadComplete();
void StartOverlappedRead();

void WaitForIO(bool Wait)
{
    HANDLE hEvents[2];
    int iEvent = 0;
    int iReadEvent = -1;
    int iWriteEvent = -1;
    if (OutstandingRead) {
        hEvents[iEvent] = ReadOverlapped.hEvent; 
        iReadEvent = iEvent;
        iEvent++;
    }
    if (OutstandingWrite) {
        hEvents[iEvent] = WriteOverlapped.hEvent; 
        iWriteEvent = iEvent;
        iEvent++;
    }

    DWORD dwStatus = WaitForMultipleObjects(iEvent, hEvents, FALSE, Wait ? INFINITE : 250 /*ms*/);
    int Index = -2;
    switch (dwStatus)
    {
    case WAIT_OBJECT_0: Index = 0; break;
    case WAIT_OBJECT_0 + 1: Index = 1; break;
    case WAIT_TIMEOUT: return;
    default:
        ErrorExit(TEXT("WaitForMultipleObjects"));
    }

    if (Index == iReadEvent)    
    {
        if (!GetOverlappedResult(
            g_hChildStd_OUT_Rd, // handle to pipe 
            &ReadOverlapped, // OVERLAPPED structure 
            &dwBytesRead,            // bytes transferred 
            FALSE))            // do not wait 
            ErrorExit(TEXT("GetOverlappedResult"));

        OutstandingRead = false;
        if (dwBytesRead > 0) OnReadComplete();
        StartOverlappedRead();
    }
    else if (Index == iWriteEvent)
    {
        if (!GetOverlappedResult(
            g_hChildStd_IN_Wr, // handle to pipe 
            &WriteOverlapped, // OVERLAPPED structure 
            &dwBytesWritten,            // bytes transferred 
            FALSE))            // do not wait 
            ErrorExit(TEXT("GetOverlappedResult"));

        if (dwBytesWritten != dwBytesToWrite) ErrorExit(TEXT("Write incomplete."));
        OutstandingWrite = false;
    }
    else ErrorExit(TEXT("WaitForMultipleObjects indexing"));
}

void WriteToPipe(string text)
{   
    BOOL bSuccess = FALSE;

    printf("Writing: %s\n", text.c_str());

    if (!OverlappedStdInWr)
    {
        bSuccess = WriteFile(g_hChildStd_IN_Wr, text.c_str(), (DWORD)text.length(), &dwBytesWritten, NULL);
        if (!bSuccess) ErrorExit(TEXT("WriteToPipe"));
        return;
    }
    else
    {
        while (OutstandingWrite) WaitForIO(true);       // Can only have one outstanding write at a time.

        WriteOverlapped.Offset = 0;
        WriteOverlapped.OffsetHigh = 0;
        WriteOverlapped.Pointer = nullptr;

        if (text.length() > BUFSIZE) ErrorExit(TEXT("Attempt to write too long a message!"));
        CopyMemory(chWriteBuf, text.c_str(), text.length());
        dwBytesToWrite = text.length();

        bSuccess = WriteFile(g_hChildStd_IN_Wr, chWriteBuf, dwBytesToWrite, &dwBytesWritten, &WriteOverlapped);
        if (bSuccess) return;
        if (!bSuccess)
        {
            if (GetLastError() == ERROR_IO_PENDING) {
                OutstandingWrite = true;
                return;
            }
            ErrorExit(TEXT("WriteToPipe"));
        }
    }
}

void OnReadComplete()
{
    chReadBuf[dwBytesRead] = '\0';
    printf("Rx: ");
    for (DWORD ii = 0; ii < dwBytesRead; ii++)
    {
        if (chReadBuf[ii] >= 0x20 && chReadBuf[ii] <= 0x7e) printf("%c", chReadBuf[ii]);
        else
        {
            printf("\\0x%02X", chReadBuf[ii]);
        }
        if (chReadBuf[ii] == '\n') printf("\n");
    }
    printf("\n");
}

void StartOverlappedRead()
{
    int loops = 0;
    for (;; loops++)
    {
        if (loops > 10) ErrorExit(TEXT("Read stuck in loop"));

        assert(!OutstandingRead);
        ReadOverlapped.Offset = 0;
        ReadOverlapped.OffsetHigh = 0;
        ReadOverlapped.Pointer = nullptr;

        BOOL Success = ReadFile(g_hChildStd_OUT_Rd, chReadBuf, BUFSIZE - 1, &dwBytesRead, &ReadOverlapped);
        if (!Success && GetLastError() != ERROR_IO_PENDING)
            ErrorExit(TEXT("ReadFile"));
        if (Success)
        {
            if (dwBytesRead > 0)
                OnReadComplete();
            continue;
        }
        else {
            OutstandingRead = true; return;
        }
    }
}

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

    BOOL bSuccess = FALSE;

    if (!OverlappedStdOutRd)
    {       
        for (;;)
        {
            DWORD total_available_bytes;
            if (FALSE == PeekNamedPipe(g_hChildStd_OUT_Rd,
                0,
                0,
                0,
                &total_available_bytes,
                0))
            {
                ErrorExit(TEXT("ReadFromPipe - peek"));
                return;
            }
            else if (total_available_bytes == 0)
            {
                // printf("No new pipe data to read at this time.\n");
                return;
            }

            bSuccess = ReadFile(g_hChildStd_OUT_Rd, chReadBuf, BUFSIZE - 1, &dwBytesRead, NULL);
            if (!bSuccess) ErrorExit(TEXT("ReadFromPipe"));
            if (dwBytesRead == 0) return;

            OnReadComplete();
        }
    }
    else
    {
        if (!OutstandingRead) StartOverlappedRead();        

        WaitForIO(false);       
    }
}

void Create()
{
    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;

    if (!OverlappedStdOutRd)
    {
        // As per the MS example, create anonymous pipes

        // 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"));
    }
    else
    {   
        // Create overlapped I/O pipes (only one side is overlapped).
        if (!MyCreatePipeEx(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0, FILE_FLAG_OVERLAPPED, 0))
            ErrorExit(TEXT("Stdout MyCreatePipeEx"));

        ZeroMemory(&ReadOverlapped, sizeof(ReadOverlapped));        
        ReadOverlapped.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);    // Manual-reset event, unnamed, initially signalled.        
        if (ReadOverlapped.hEvent == NULL)
            ErrorExit(TEXT("CreateEvent Read"));
    }

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

    if (!OverlappedStdInWr)
    {
        // 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"));
    }
    else
    {
        // Create overlapped I/O pipes (only one side is overlapped).
        if (!MyCreatePipeEx(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0, 0, FILE_FLAG_OVERLAPPED))
            ErrorExit(TEXT("Stdin MyCreatePipeEx"));

        ZeroMemory(&WriteOverlapped, sizeof(WriteOverlapped));
        WriteOverlapped.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);   // Manual-reset event, unnamed, initially signalled.        
        if (WriteOverlapped.hEvent == NULL)
            ErrorExit(TEXT("CreateEvent Write"));
    }

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

    TCHAR* szMutableCmdline = new TCHAR[1024];  
    ZeroMemory(szMutableCmdline, 1024 * sizeof(TCHAR));
    CopyMemory(szMutableCmdline, szCmdline, _tcslen(szCmdline) * sizeof(TCHAR));
    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,
        szMutableCmdline,     // 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);
    }
}

int main()
{
    printf("Launching...\n");
    Create();
    Sleep(500);
    ReadFromPipe(); 
    Sleep(250);
    WriteToPipe("A\r\n"); 
    Sleep(250);
    ReadFromPipe();
    WriteToPipe("\r\n"); 
    Sleep(250);
    ReadFromPipe(); 
    WriteToPipe("X\r\n"); 
    Sleep(250);
    ReadFromPipe();
    Sleep(250);
    ReadFromPipe(); 
    printf("Press any key to exit.\n");
    _getch();

    // TODO: Not doing proper cleanup in this test app.  Overlapped I/O, CloseHandles, etc. are outstanding.  Bad.

    return 0;
}

子代码可以很简单:

#include <conio.h>

int main()
{
    printf("Hello!\n");
    _getch();
    printf("Bye!\n");
    return 0;
}

编辑:正如@Rbmm所指出的,_getch()使用ReadConsoleInput()。我假设它使用CONIN $而不是STDIN。所以问题就变成了:我可以重定向CONIN $还是让父进程写入它?

1 个答案:

答案 0 :(得分:1)

printf之后的子进程中,您可以添加fflush(stdout);。这将立即将数据从标准输出缓冲区传输到管道。在某些配置中,stdout缓冲区数据会在行字符\n的末尾自动刷新,但是我不确定是否在这种情况下-可能不是。

如果您的孩子应该从管道(而不是从控制台)读取数据,请使用getcharfgetsfreadfscanf,将stdin用作流参数

int main()
{
    printf("Hello!\n");
    fflush(stdout);
    getchar();
    printf("Bye!\n");
    fflush(stdout);
    return 0;
}

您没有死锁。您的孩子只是在等待来自控制台的字符。按Enter键将其恢复。