子进程使用管道进行的I/O重定向 - WinAPI

3

我正在处理一个提供API的应用程序,这样脚本编写就更容易了。基本上,当您输入有效的输入时,它会输出一个答案。我想使用该输出发送更多的输入,例如:

Input: <nodes>
Output: 1, 56, 23
Input <56>
Output: "Apple"

我想要做的是一个程序,它可以向目标进程的STDIN写入内容并读取其STDOUT的输出。为此,我主要参考了以下代码:在Windows中创建具有重定向输入和输出的子进程 - MSDN。唯一的问题是,在从子进程读取之前,我需要关闭用于写入它的父进程句柄,这意味着我不能使用子进程的输出来再次写入更多内容。下面是简化后的代码,取自MSDN:
#include <Windows.h>
#include <string>

using std::string;

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

#define BUFSIZE 4096

void WriteToPipe(string msg); 
void ReadFromPipe(void); 

int main()
{
    /*
     * CREATE PIPES
    */

    SECURITY_ATTRIBUTES saAttr;

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

    // Create pipes for the child process's STDOUT and STDIN,
    // ensures both handle are not inherited
    CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0);
    SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0);

    CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0);
    SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0);

    /*
     * CREATE CHILD PROCESS
    */

    TCHAR szCmdline[]=TEXT("target.exe");
    STARTUPINFO siStartInfo;
    PROCESS_INFORMATION piProcInfo;

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

    CreateProcess(NULL, szCmdline, NULL, NULL, TRUE, 0,
                  NULL,  NULL, &siStartInfo, &piProcInfo);

    // 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(g_hChildStd_OUT_Wr);
    CloseHandle(g_hChildStd_IN_Rd);
    CloseHandle(piProcInfo.hProcess);
    CloseHandle(piProcInfo.hThread);

    /*
     * ACTUAL APPLICATION
    */

    WriteToPipe("<nodes>\n");

    // Need to close the handle before reading
    CloseHandle(g_hChildStd_IN_Wr); // PROBLEM HERE

    ReadFromPipe();

    WriteToPipe("<56>\n"); // I can't, as I have released the handle already

    system("pause");

    return 0;
}

void WriteToPipe(string msg)
{
    WriteFile(g_hChildStd_IN_Wr, msg.c_str(), msg.size(), NULL, NULL);
}

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

问题出在这一行代码:
CloseHandle(g_hChildStd_IN_Wr);

要么我把它留在那里,读取一次后就无法写入子进程,要么我将其移除,然后ReadFromPipe会陷入死锁。

任何帮助都将不胜感激,谢谢!


1
要从子进程读取数据,我首先需要关闭父进程用于向其写入数据的句柄。不,你不需要这样做,总会有一种方法可以解决。我曾经做过,只是需要回忆一下如何实现。 - Mooing Duck
如果我移除掉“CloseHandle(g_hChildStd_IN_Wr);”这一行代码,程序会卡在ReadFromPipe方法的第"bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);"这一行代码。虽然我不确定具体原因,但这确实发生了。有什么解决办法吗? - MyUsername112358
是的,这就是我刚刚尝试过的!但出于某种原因,“result = PeekNamedPipe(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, &dwAvailableBytes, &dwBytesLeft);”总是成功,但无论是否有字节可读,dwRead、dwAvailableBytes和dwBytesLeft始终被设置为0。我会再仔细研究一下,也许我错过了什么... - MyUsername112358
2
你可能在进程写入之前进行了检查。你需要不时地继续检查。 - Mooing Duck
当然,我太傻了!非常感谢你的帮助,多亏了你,我终于把它搞定了! - MyUsername112358
1个回答

4

ReadFile(..., BUFSIZE) 意味着“等待程序写入BUFSIZE 字节,或关闭它的管道端口”。该程序只写入了少量字节,然后等待更多输入,而您没有提供。如果您关闭写入管道,则它知道没有更多输入,并退出,此时您的 ReadFile 就知道没有更多输入并返回。您需要找到一种读取与管道中相同数量字节的方法。

关键在于PeekNamedPipe,它告诉您程序已输出多少数据,因此可以读取多少数据而不会阻塞。请注意,您需要定期检查是否自上次检查以来程序写入了更多字节。


1
记录一下,第一句话并不完全正确。管道是一个特殊情况;每当在管道的另一端完成写操作时,ReadFile 就会退出。 (这在这种特定情况下并没有太大帮助,但在某些情况下,它可以极大地简化事情。) - Harry Johnston

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接