Как я могу прочитать вывод процесса, который не был сброшен?

Считайте, что эта маленькая программа будет скомпилирована как application.exe

#include <stdio.h>

int main()
{
    char str[100];
    printf ("Hello, please type something\n");
    scanf("%[^\n]s", &str);
    printf("you typed: %s\n", str);
    return 0;
}

Теперь я использую этот код для запуска application.exe и получить его вывод.

#include <stdio.h>
#include <iostream>
#include <stdexcept>

int main()
{
    char buffer[128];
    FILE* pipe = popen("application.exe", "r");
    while (!feof(pipe)) {
        if (fgets(buffer, 128, pipe) != NULL)
            printf(buffer);
    }
    pclose(pipe);
    return 0;
}

Моя проблема в том, что нет вывода, пока я не сделал свой ввод. Затем обе выходные строки извлекаются. Я могу обойти эту проблему, добавив эту строку после первого printf заявление.

fflush(stdout);

Затем первая строка извлекается, прежде чем я сделаю свой ввод, как ожидалось.

Но как я могу получить выходные данные приложений, которые я не могу изменить и которые не используют fflush() в "реальном времени" (значит, перед тем как выйти)?, И как Windows CMD делает это?

5 ответов

Решение

Проблемы моего вопроса в моем первоначальном посте уже очень хорошо объяснены в других ответах.
Консольные приложения используют функцию с именем isatty() обнаружить, если их stdout Обработчик подключен к трубе или реальной консоли. В случае канала весь вывод буферизуется и сбрасывается порциями, кроме случаев, когда вы напрямую вызываете fflush(), В случае реальной консоли вывод небуферизован и напрямую выводится на вывод консоли.
В Linux вы можете использовать openpty() создать псевдотерминал и создать в нем свой процесс. В результате процесс будет думать, что он работает в реальном терминале и использует небуферизованный вывод.
Windows, кажется, не имеет такой опции.

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

#include <windows.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    char cmdline[] = "application.exe"; // process command
    HANDLE scrBuff;                     // our virtual screen buffer
    CONSOLE_SCREEN_BUFFER_INFO scrBuffInfo; // state of the screen buffer
                                            // like actual cursor position
    COORD scrBuffSize = {80, 25};       // size in chars of our screen buffer
    SECURITY_ATTRIBUTES sa;             // security attributes
    PROCESS_INFORMATION procInfo;       // process information
    STARTUPINFO startInfo;              // process start parameters
    DWORD procExitCode;                 // state of process (still alive)
    DWORD NumberOfCharsWritten;         // output of fill screen buffer func
    COORD pos = {0, 0};                 // scr buff pos of data we have consumed
    bool quit = false;                  // flag for reading loop

    // 1) Create a screen buffer, set size and clear

    sa.nLength = sizeof(sa);
    scrBuff = CreateConsoleScreenBuffer( GENERIC_READ | GENERIC_WRITE,
                                         FILE_SHARE_READ | FILE_SHARE_WRITE,
                                         &sa, CONSOLE_TEXTMODE_BUFFER, NULL);
    SetConsoleScreenBufferSize(scrBuff, scrBuffSize);
    // clear the screen buffer
    FillConsoleOutputCharacter(scrBuff, '\0', scrBuffSize.X * scrBuffSize.Y,
                               pos, &NumberOfCharsWritten);

    // 2) Create and start a process
    //      [using our screen buffer as stdout]

    ZeroMemory(&procInfo, sizeof(PROCESS_INFORMATION));
    ZeroMemory(&startInfo, sizeof(STARTUPINFO));
    startInfo.cb = sizeof(STARTUPINFO);
    startInfo.hStdOutput = scrBuff;
    startInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
    startInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
    startInfo.dwFlags |= STARTF_USESTDHANDLES;
    CreateProcess(NULL, cmdline, NULL, NULL, FALSE,
                  0, NULL, NULL, &startInfo, &procInfo);    
    CloseHandle(procInfo.hThread);

    // 3) Read from our screen buffer while process is alive

    while(!quit)
    {
        // check if process is still alive or we could quit reading
        GetExitCodeProcess(procInfo.hProcess, &procExitCode);
        if(procExitCode != STILL_ACTIVE) quit = true;

        // get actual state of screen buffer
        GetConsoleScreenBufferInfo(scrBuff, &scrBuffInfo);

        // check if screen buffer cursor moved since
        // last time means new output was written
        if (pos.X != scrBuffInfo.dwCursorPosition.X ||
            pos.Y != scrBuffInfo.dwCursorPosition.Y)            
        {
            // Get new content of screen buffer
            //  [ calc len from pos to cursor pos: 
            //    (curY - posY) * lineWidth + (curX - posX) ]
            DWORD len =  (scrBuffInfo.dwCursorPosition.Y - pos.Y)
                        * scrBuffInfo.dwSize.X 
                        +(scrBuffInfo.dwCursorPosition.X - pos.X);
            char buffer[len];
            ReadConsoleOutputCharacter(scrBuff, buffer, len, pos, &len);

            // Print new content
            // [ there is no newline, unused space is filled with '\0'
            //   so we read char by char and if it is '\0' we do 
            //   new line and forward to next real char ]
            for(int i = 0; i < len; i++)
            {
                if(buffer[i] != '\0') printf("%c",buffer[i]);
                else
                {
                    printf("\n");
                    while((i + 1) < len && buffer[i + 1] == '\0')i++;
                }
            }

            // Save new position of already consumed data
            pos = scrBuffInfo.dwCursorPosition;
        }
        // no new output so sleep a bit before next check
        else Sleep(100);
    }

    // 4) Cleanup and end

    CloseHandle(scrBuff);   
    CloseHandle(procInfo.hProcess);
    return 0;
}

Вас укусил тот факт, что буферизация для потоков, которые автоматически открываются в C-программе, изменяется в зависимости от типа подключенного устройства.

Это немного странно - одна из вещей, с которой * nixes приятно играть (и которые отражены в стандартной библиотеке C), заключается в том, что процессам не очень важно знать, откуда они получают данные и куда они их записывают. Вы просто транслируете и перенаправляете на досуге, и это обычно подключи и играй, и довольно быстро.

Одно очевидное место, где нарушается это правило - это взаимодействие; Вы представляете хороший пример. Если выходные данные программы буферизованы блоком, вы не увидите их до того, как, возможно, накопятся данные 4k или процесс завершится.

Программа может определить, пишет ли она в терминал через isatty() (и, возможно, с помощью других средств). Терминал концептуально включает в себя пользователя, предлагающего интерактивную программу. Код библиотеки, открывающий stdin и stdout, проверяет это и изменяет свою политику буферизации на строку с буферизацией: при обнаружении новой строки поток сбрасывается. Это идеально подходит для интерактивных, линейно-ориентированных приложений. (Это менее чем идеально для редактирования строк, как это делает bash, который полностью отключает буферизацию.)

Страница руководства по открытой группе для stdin довольно расплывчата в отношении буферизации, чтобы дать реализациям достаточно возможностей для эффективности, но она говорит:

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

Вот что происходит с вашей программой: стандартная библиотека видит, что она работает "неинтерактивно" (запись в канал), пытается быть умной и эффективной и включает буферизацию блоков. Запись новой строки больше не сбрасывает вывод. Обычно это хорошо: представьте, что вы записываете двоичные данные, записываете на диск в среднем каждые 256 байтов! Грозный.

Следует отметить, что между вами и, скажем, диском, вероятно, существует целый каскад буферов; после стандартной библиотеки C появляются буферы операционной системы, а затем и диск.

Теперь к вашей проблеме: стандартный буфер библиотеки, используемый для хранения записываемых символов, находится в памяти программы. Несмотря на внешний вид, данные еще не покинули вашу программу и, следовательно, (официально) не доступны для других программ. Я думаю, что вам не повезло. Вы не одиноки: большинство интерактивных консольных программ будут работать плохо, если вы попытаетесь управлять ими через каналы.

ИМХО, это одна из менее логичных частей буферизации ввода-вывода: она действует по-разному, когда направляется на терминал, в файл или канал. Если IO направлен на файл или канал, он обычно буферизуется, это означает, что вывод фактически записывается только тогда, когда буфер заполнен или когда происходит явное очищение => - это то, что вы видите, когда выполняете программу через popen,

Но когда IO направляется на терминал, возникает особый случай: все ожидающие выходные данные автоматически сбрасываются перед чтением с того же терминала. Этот особый случай необходим, чтобы позволить интерактивным программам отображать подсказки перед чтением.

Плохо то, что если вы попытаетесь провести интерактивное приложение через каналы, вы потеряете: подсказки могут быть прочитаны только тогда, когда приложение завершится или когда будет выведено достаточно текста для заполнения буфера. Вот почему разработчики Unix изобрели так называемые псевдо-тты (pty). Они реализованы как драйверы терминала, так что приложение использует интерактивную буферизацию, но IO фактически управляется другой программой, владеющей главной частью pty.

К сожалению, как ты пишешь application.exe Я предполагаю, что вы используете Windows, и я не знаю эквивалентного механизма в Windows API. Вызываемый должен использовать небуферизованный ввод-вывод (stderr по умолчанию небуферизовано), чтобы вызывающий абонент мог прочитать подсказки перед отправкой ответа.

Ты не можешь Потому что еще не сброшенные данные принадлежат самой программе.

Я думаю, что вы можете сбросить данные в stderr или инкапсулировать функцию fgetc а также fungetc не портить поток или использовать system("application.ext >>log") а потом mmap войти в память, чтобы делать то, что вы хотите.

Другие вопросы по тегам