Перенаправление ввода-вывода из дочернего процесса с использованием каналов — winapi

Я работаю с приложением, которое предлагает 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 застрянет в тупике.

Любая помощь будет оценена, спасибо!


person MyUsername112358    schedule 16.04.2015    source источник
comment
чтобы читать из дочернего процесса, мне сначала нужно закрыть дескриптор моего родительского процесса, который использовался для записи в него. Нет, вы этого не сделаете, где-то есть способ сделать это. Я сделал это один раз, мне просто нужно помнить, как   -  person Mooing Duck    schedule 16.04.2015
comment
Если я удалю строку CloseHandle(g_hChildStd_IN_Wr);, программа застрянет в ReadFromPipe в строке bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);. Хотя я не совсем уверен, почему это происходит, но это так... какое-нибудь решение?   -  person MyUsername112358    schedule 16.04.2015
comment
Да, это то, что я только что попробовал! Но по какой-то причине результат = PeekNamedPipe(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, &dwAvailableBytes, &dwBytesLeft); всегда завершается успешно, но dwRead, dwAvailableBytes и dwBytesLeft всегда устанавливаются равными 0, независимо от того, есть байты для чтения или нет. Посмотрю подробнее, может что-то пропустил...   -  person MyUsername112358    schedule 16.04.2015
comment
Вы наверное проверяли до того как процесс написал. Вам придется время от времени проверять.   -  person Mooing Duck    schedule 16.04.2015
comment
Конечно, я тупой! Большое спасибо за вашу помощь, благодаря вам у меня все получилось!   -  person MyUsername112358    schedule 16.04.2015


Ответы (1)


ReadFile(..., BUFSIZE) означает "подождать, пока программа не запишет BUFSIZE байта или не закроет конец канала". Программа записывает небольшое количество байтов, а затем ожидает ввода данных, которые вы не предоставляете. Если вы закроете канал записи, он узнает, что ввода больше нет, и завершит работу, после чего ваш ReadFile узнает, что ввода больше нет, и вернется. Вам нужно найти способ прочитать только столько байтов, сколько находится в канале.

Волшебным элементом здесь является PeekNamedPipe, который говорит вам, сколько данных выводит программа и, следовательно, сколько вы можете прочитать без блокировки. Обратите внимание, что вам придется время от времени проверять, не записала ли программа больше байтов с момента последней проверки.

person Mooing Duck    schedule 16.04.2015
comment
Кстати, первое предложение не совсем правильное. Трубы — это особый случай; ReadFile будет выходить каждый раз, когда операция записи завершается на другой стороне канала. (Это мало помогает в данном конкретном сценарии, но в некоторых контекстах значительно упрощает работу.) - person Harry Johnston; 29.06.2017