Как я могу прочитать вывод процесса, который не был сброшен?
Считайте, что эта маленькая программа будет скомпилирована как 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
войти в память, чтобы делать то, что вы хотите.