Захват вывода из консольной программы с перекрытием и событиями - PullRequest
0 голосов
/ 07 июня 2019

Я знаю, что много подобных вопросов по этой теме задавалось ранее, но до сих пор я не смог найти решение, которое действительно работает.Я хочу запустить консольную программу из моей программы и записать ее вывод.Моя реализация должна быть совместима с WaitForMultipleObjects(), т.е. я хочу получать уведомления всякий раз, когда в канале появляются новые данные для чтения.

Моя реализация основана на этот пример из MSDN.Однако мне пришлось немного его изменить, потому что мне нужен перекрывающийся ввод-вывод, чтобы можно было дождаться завершения ReadFile().Поэтому я использую именованные каналы, созданные с помощью функции MyCreatePipeEx() Дейва Харта из здесь .

Это мой настоящий код.Я удалил проверки ошибок для удобства чтения.

HANDLE hReadEvent;
HANDLE hStdIn_Rd, hStdIn_Wr;
HANDLE hStdOut_Rd, hStdOut_Wr;
SECURITY_ATTRIBUTES saAttr; 
PROCESS_INFORMATION piProcInfo; 
STARTUPINFO siStartInfo;
OVERLAPPED ovl;
HANDLE hEvt[2];
DWORD mask, gotbytes;
BYTE buf[4097];

saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
saAttr.bInheritHandle = TRUE; 
saAttr.lpSecurityDescriptor = NULL; 

MyCreatePipeEx(&hStdOut_Rd, &hStdOut_Wr, &saAttr, 0, FILE_FLAG_OVERLAPPED, FILE_FLAG_OVERLAPPED);        
MyCreatePipeEx(&hStdIn_Rd, &hStdIn_Wr, &saAttr, 0, FILE_FLAG_OVERLAPPED, FILE_FLAG_OVERLAPPED); 

SetHandleInformation(hStdOut_Rd, HANDLE_FLAG_INHERIT, 0);
SetHandleInformation(hStdIn_Wr, HANDLE_FLAG_INHERIT, 0);

memset(&piProcInfo, 0, sizeof(PROCESS_INFORMATION));
memset(&siStartInfo, 0, sizeof(STARTUPINFO));

siStartInfo.cb = sizeof(STARTUPINFO); 
siStartInfo.hStdError = hStdOut_Wr;
siStartInfo.hStdOutput = hStdOut_Wr;
siStartInfo.hStdInput = hStdIn_Rd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

CreateProcess(NULL, "test.exe", NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo);

hReadEvent = CreateEvent(NULL, TRUE, FALSE, NULL);      

for(;;) {

    int i = 0;

    hEvt[i++] = piProcInfo.hProcess;

    memset(&ovl, 0, sizeof(OVERLAPPED));
    ovl.hEvent = hReadEvent;

    if(!ReadFile(hStdOut_Rd, buf, 4096, &gotbytes, &ovl)) {     
        if(GetLastError() == ERROR_IO_PENDING) hEvt[i++] = hReadEvent;          
    } else {
        buf[gotbytes] = 0;
        printf("%s", buf);
    }               

    mask = WaitForMultipleObjects(i, hEvt, FALSE, INFINITE);

    if(mask == WAIT_OBJECT_0 + 1) {

        if(GetOverlappedResult(hStdOut_Rd, &ovl, &gotbytes, FALSE)) {                   
            buf[gotbytes] = 0;
            printf("%s", buf);
        }

    } else if(mask == WAIT_OBJECT_0) {

        break;
    }   
}

Проблема с этим кодом заключается в следующем: Как вы можете видеть, я читаю порциями по 4 КБ, используя ReadFile(), потому что я, очевидно, незнать, сколько данных будет выводить внешняя программа test.exe.Для этого было предложено здесь :

Чтобы прочитать переменное количество данных из клиентского процесса , просто отправьте запросы на чтение любого удобного вам размера и будьте готовы обрабатывать события чтения, которые короче, чем вы запрашивали.Не интерпретируйте короткую, но ненулевую длину как EOF.Продолжайте выдавать запросы на чтение, пока не получите чтение нулевой длины или ошибку.

Однако это не работает.Объект события, переданный в ReadFile() как часть структуры OVERLAPPED, сработает только тогда, когда в буфере будет 4 КБ.Если внешняя программа просто печатает «Hello», событие вообще не сработает.Для фактического запуска hReadEvent в буфере должно быть 4 КБ.

Поэтому я подумал, что вместо этого я должен читать байт за байтом, и изменил свою программу так, чтобы использовать ReadFile() следующим образом:

if(!ReadFile(hStdOut_Rd, buf, 1, &gotbytes, &ovl)) {

Однако это тоже не работает.Если я делаю это так, событие чтения вообще не запускается, что меня действительно смущаетПри использовании 4096 байтов событие действительно срабатывает, как только в канале есть 4096 байтов, но при использовании 1 байта оно вообще не работает.

Итак, как мне решить эту проблему?У меня практически нет идей здесь.Нет ли способа вызвать триггер ReadFile(), когда в канале появляются новые данные?Не может быть так сложно, не так ли?

1 Ответ

0 голосов
/ 08 июня 2019

Только для записи, хотя с моим кодом есть некоторые проблемы (см. Обсуждение в комментариях ниже ОП), общая проблема заключается в том, что на самом деле невозможно получить выходные данные произвольных внешних программ, поскольку они обычно используют буферизацию блоков. когда их выходные данные перенаправляются в канал, это означает, что выходные данные поступят в программу захвата только после того, как этот буфер очищен, поэтому захват в реальном времени невозможен.

Однако были предложены некоторые обходные пути:

1) (Windows) Здесь - это обходной путь, использующий GetConsoleScreenBuffer() для захвата вывода из произвольных консольных программ, но в настоящее время он поддерживает только одну консольную страницу вывода.

2) (Linux) В Linux, по-видимому, можно использовать псевдо-терминалы, чтобы заставить внешнюю программу использовать небуферизованный вывод. Здесь является примером.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...