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

标签 c windows winapi visual-studio-2015 createprocess

我正在尝试使用 CreateProcess() 启动一个进程,并将标准输入和标准输出重定向到管道。当子进程仅包含 printf() 语句时,我看到它们通过管道传输到父进程并显示得很好。如果我的子进程执行 printf() 和 _getch() 语句,那么事情就会失败。我考虑过可能的deadlock between the pipes尝试了多种方法均无效:

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

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

同样,如果子进程仅发出 printfs 但因 _getch() 失败,则可以工作。值得注意的是,如果子程序中存在 _getch(),则即使 printfs 也不会显示 - 即使 printfs() 在 _getch() 之前发出。我读过管道有缓冲,如上所述,它们在管道的另一端有潜在的死锁等待,但我想不出除了下面所做的之外我还能做些什么来避免这种情况。

为了以防万一,我还确保我有一个大的堆缓冲区用于 command-line buffer since CreateProcess() is known to modify it .

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

#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$ 或者让父进程写入它吗?

最佳答案

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

如果您的 child 应该从管道(而不是控制台)读取数据,请使用 getcharfgetsfreadfscanf 给它们 stdin 作为流参数。

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

而且你没有死锁。您的 child 只是等待来自控制台的字符。按 Enter 键即可恢复它。

关于子进程(通过 CreateProcess)在带有重定向 stdout 和 stdin 的 getch() 上停止,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51803083/

相关文章:

c - 如何修剪该数组中的空格(右侧)?

python - 除了性能之外,python 应用程序使用 C 有什么目的吗?

c - 如何检查内存区域是否映射到文件?

c - 错误: Illegal Instruction. Mips交叉编译器

windows - Window 的命令行 (cmd.exe) 命令的最大长度是多少?

windows - 使用 Docker 在 Windows 上运行 Ubuntu 服务器并使用 IDE 进行开发的最佳方法是什么?

c++ - 强制资源从非 mfc 应用程序中的 dll 加载的 WTL 方式? (我们使用的是 WTL/ATL,而不是直接的 win32)

c - 如何在 Windows 上用 C 编写 unicode hello world

c# - SetupDi API : Control Device Manager functionality programmatically

c++ - 从 std::ofstream 获取句柄