I/O пренасочване от дъщерен процес с помощта на канали - 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