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

Рассмотрим эту небольшую программу, которая будет скомпилирована как 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?

Ответ 1

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

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

#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;
}

Ответ 2

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

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

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

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

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

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

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

Ответ 5

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