Search code examples
c++winapiprocessstdoutunbuffered

How can I read process output that has not been flushed?


Consider this little programm be compiled as 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;
}

Now I use this code to start application.exe and fetch its output.

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

My problem is that there is no output until I did my input. Then both output lines get fetched. I can workarround this problem by adding this line after the first printf statement.

fflush(stdout);

Then the first line is fetched before I do my input as expected.

But how can I fetch output of applications that I cannot modify and that do not use fflush() in "realtime" (means before they exit)? . And how does the windows cmd do it?


Solution

  • The problems of my question in my original post are already very good explained in the other answers.
    Console applications use a function named isatty() to detect if their stdout handler is connected to a pipe or a real console. In case of a pipe all output is buffered and flushed in chunks except if you directly call fflush(). In case of a real console the output is unbuffered and gets directly printed to the console output.
    In Linux you can use openpty() to create a pseudoterminal and create your process in it. As a result the process will think it runs in a real terminal and uses unbuffered output.
    Windows seems not to have such an option.

    After a lot of digging through winapi documentation I found that this is not true. Actually you can create your own console screen buffer and use it for stdout of your process that will be unbuffered then.
    Sadly this is not a very comfortable solution because there are no event handler and we need to poll for new data. Also at the moment I'm not sure how to handle scrolling when this screen buffer is full.
    But even if there are still some problems left I think I have created a very useful (and interesting) starting point for those of you who ever wanted to fetch unbuffered (and unflushed) windows console process output.

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