C++ 如何读取尚未刷新的进程输出?
考虑将这个小程序编译为C++ 如何读取尚未刷新的进程输出?,c++,winapi,process,stdout,unbuffered,C++,Winapi,Process,Stdout,Unbuffered,考虑将这个小程序编译为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>
int main()
{
char str[100];
printf ("Hello, please type something\n");
scanf("%[^\n]s", &str);
printf("you typed: %s\n", str);
return 0;
}
我的问题是在我输入之前没有输出。然后获取两个输出行。
我可以通过在第一个printf
语句后添加此行来解决此问题
fflush(stdout);
然后在我按预期输入之前获取第一行
但是如何获取我无法修改且未在“实时”(即退出前)中使用fflush()
的应用程序的输出。
windows cmd是如何实现的?你不能。
因为尚未刷新的数据属于程序本身。我认为您可以将数据刷新到
stderr
或封装fgetc
和fungetc
的函数,以避免损坏流或使用系统(“application.ext>>log”)
然后mmap
记录到内存中去做你想做的事情。你已经被C程序中自动打开的流的缓冲区随着连接的设备类型的变化而改变这一事实所困扰
这有点奇怪-使*nixes很好使用的一个因素(反映在C标准库中)是进程不太关心从何处获取数据以及从何处写入数据。你只是在闲暇时用管道和重定向,它通常是即插即用的,而且速度非常快
一个明显的地方,这一规则打破了互动;你举了一个很好的例子。如果程序的输出是块缓冲的,那么在4k数据积累之前,或者在进程退出之前,您看不到它
程序可以通过isatty()
(也可能通过其他方式)检测是否写入终端。终端概念上包括一个用户,建议一个交互程序。打开stdin和stdout的库代码检查并将其缓冲策略更改为行缓冲:当遇到换行符时,将刷新流。这非常适合交互式、面向行的应用程序。(它不太适合行编辑,因为bash完全禁用缓冲。)
为了给实现足够的灵活性以提高效率,缓冲方面的定义相当模糊,但它确实指出:
当且仅当可以确定流不引用交互设备时,标准输入和标准输出流被完全缓冲
这就是您的程序所发生的情况:标准库看到它以“非交互方式”(写入管道)运行,尝试变得智能和高效,并切换块缓冲。写换行不再刷新输出。通常这是一件好事:想象一下写二进制数据,平均每256字节写入磁盘一次!糟透了
值得注意的是,在您和(比如)磁盘之间可能有一整串缓冲区;在C标准库之后是操作系统的缓冲区,然后是磁盘
现在解决您的问题:用于存储要写入的字符的标准库缓冲区位于程序的内存空间中。尽管有外观,但数据尚未离开您的程序,因此其他程序无法(正式)访问。我想你运气不好。并非只有你一个人:当人们试图通过管道操作大多数交互式控制台程序时,它们的性能会很差。IMHO,这是IO缓冲中逻辑性较差的部分之一:当定向到终端或文件或管道时,它的行为会有所不同。如果IO被定向到文件或管道,它通常会被缓冲,这意味着只有当缓冲区已满或显式刷新发生时,才实际写入输出=>这是通过
popen
执行程序时所看到的
但当IO被定向到终端时,会出现一种特殊情况:在从同一终端读取之前,所有挂起的输出都会自动刷新。这种特殊情况对于允许交互式程序在阅读前显示提示是必要的
糟糕的是,如果您试图通过管道驱动一个交互式应用程序,就会出现问题:只有当应用程序结束或输出足够的文本以填充缓冲区时,才能读取提示。这就是Unix开发人员发明所谓的伪TTY(pty
)的原因。它们作为终端驱动程序实现,以便应用程序使用交互式缓冲,但IO实际上是由拥有pty主部件的另一个程序操纵的
不幸的是,在编写
应用程序.exe
时,我假设您使用的是Windows,而我不知道Windows API中的等效机制。被调用者必须使用无缓冲IO(stderr
默认为无缓冲),以允许调用者在发送答案之前阅读提示。我的原始帖子中的问题已经得到了很好的解释
在其他答案中。
控制台应用程序使用名为isatty()的函数来检测
如果他们的stdout
处理程序连接到管道或真实控制台。如果是管道
除了直接调用fflush()。
对于真正的控制台,输出是无缓冲的,并直接打印到
控制台输出。
在Linux中,您可以使用openpty()
创建伪终端并在其中创建进程。作为一个
结果该进程将认为它在实际终端中运行,并使用未缓冲的输出。
Windows似乎没有
这样的选择。
在深入研究winapi文档之后,我发现这是不正确的。实际上你可以创造
您自己的控制台屏幕缓冲区,并将其用于将取消缓冲的进程的stdout
。
遗憾的是,这不是一个非常舒适的解决方案,因为没有事件处理程序,我们需要pol
fflush(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;
}