How can I redirect stdout to some visible display in a Windows Application?
Asked Answered
W

8

39

I have access to a third party library that does "good stuff." It issues status and progress messages to stdout. In a Console application I can see these messages just fine. In a Windows application they just go to the bit bucket.

Is there a fairly simple way to redirect stdout and stderr to a text control or other visible place. Ideally, this would not require any recompiles of the third party code. It would just intercept the steams at a low level. I'd like a solution where I just #include the header, call the initialization function and link the library as in...

#include "redirectStdFiles.h"

void function(args...)
{
  TextControl* text = new TextControl(args...);
  initializeRedirectLibrary(text, ...);

  printf("Message that will show up in the TextControl\n");
  std::cout << "Another message that also shows up in TextControl\n";
}

Even better would be if it used some interface that I could override so it is not tied to any particular GUI library.

class StdFilesRedirector
{
  public:
    writeStdout(std::string const& message) = 0;
    writeStderr(std::string const& errorMessage) = 0;
    readStdin(std::string &putReadStringHere) = 0;
};

Am I just dreaming? Or does anyone know of something that can do something like this?

Edit after two answers: I think using freopen to redirect the files is a good first step. For a complete solution there would need to be a new thread created to read the file and display the output. For debugging, doing a 'tail -f' in a cygwin shell window would be enough. For a more polished application... Which is what I want to write... there would be some extra work to create the thread, etc.

Wobbling answered 21/2, 2009 at 20:50 Comment(1)
AllocConsole is the missing piece. It produces the familiar console window. The read and write functions are awkward, but you can redirect stdout/stdin to/from it quite easily using this method: https://mcmap.net/q/352336/-allocconsole-not-displaying-coutJennyjeno
F
19

You need to create pipe (with CreatePipe()), then attach stdout to it's write end with SetStdHandle(), then you can read from pipe's read end with ReadFile() and put text you get from there anywhere you like.

Futility answered 21/2, 2009 at 21:16 Comment(0)
N
18

You can redirect stdout, stderr and stdin using freopen.

From the above link:

/* freopen example: redirecting stdout */
#include <stdio.h>

int main ()
{
  freopen ("myfile.txt","w",stdout);
  printf ("This sentence is redirected to a file.");
  fclose (stdout);
  return 0;
}

You can also run your program via command prompt like so:

a.exe > stdout.txt 2> stderr.txt
Neat answered 21/2, 2009 at 20:54 Comment(0)
L
16

You're probably looking for something along those lines:

    #define OUT_BUFF_SIZE 512

    int main(int argc, char* argv[])
    {
        printf("1: stdout\n");

        StdOutRedirect stdoutRedirect(512);
        stdoutRedirect.Start();
        printf("2: redirected stdout\n");
        stdoutRedirect.Stop();

        printf("3: stdout\n");

        stdoutRedirect.Start();
        printf("4: redirected stdout\n");
        stdoutRedirect.Stop();

        printf("5: stdout\n");

        char szBuffer[OUT_BUFF_SIZE];
        int nOutRead = stdoutRedirect.GetBuffer(szBuffer,OUT_BUFF_SIZE);
        if(nOutRead)
            printf("Redirected outputs: \n%s\n",szBuffer);

        return 0;
    }

This class will do it:

#include <windows.h>

#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <iostream>

#ifndef _USE_OLD_IOSTREAMS
using namespace std;
#endif

#define READ_FD 0
#define WRITE_FD 1

#define CHECK(a) if ((a)!= 0) return -1;

class StdOutRedirect
{
    public:
        StdOutRedirect(int bufferSize);
        ~StdOutRedirect();

        int Start();
        int Stop();
        int GetBuffer(char *buffer, int size);

    private:
        int fdStdOutPipe[2];
        int fdStdOut;
};

StdOutRedirect::~StdOutRedirect()
{
    _close(fdStdOut);
    _close(fdStdOutPipe[WRITE_FD]);
    _close(fdStdOutPipe[READ_FD]);
}
StdOutRedirect::StdOutRedirect(int bufferSize)
{
    if (_pipe(fdStdOutPipe, bufferSize, O_TEXT)!=0)
    {
        //treat error eventually
    }
    fdStdOut = _dup(_fileno(stdout));
}

int StdOutRedirect::Start()
{
    fflush( stdout );
    CHECK(_dup2(fdStdOutPipe[WRITE_FD], _fileno(stdout)));
    ios::sync_with_stdio();
    setvbuf( stdout, NULL, _IONBF, 0 ); // absolutely needed
    return 0;
}

int StdOutRedirect::Stop()
{
    CHECK(_dup2(fdStdOut, _fileno(stdout)));
    ios::sync_with_stdio();
    return 0;
}

int StdOutRedirect::GetBuffer(char *buffer, int size)
{
    int nOutRead = _read(fdStdOutPipe[READ_FD], buffer, size);
    buffer[nOutRead] = '\0';
    return nOutRead;
}

Here's the result:

1: stdout
3: stdout
5: stdout
Redirected outputs:
2: redirected stdout
4: redirected stdout
Lecroy answered 21/2, 2009 at 20:50 Comment(1)
Why keep calling ios::sync_with_stdio(true) with each Start() and Stop()? Is there other code somewhere setting it to false?Loudmouthed
B
7

When you create a process using CreateProcess() you can choose a HANDLE to which stdout and stderr are going to be written. This HANDLE can be a file to which you direct the output.

This will let you use the code without recompiling it. Just execute it and instead of using system() or whatnot, use CreateProcess().

The HANDLE you give to CreateProcess() can also be that of a pipe you created, and then you can read from the pipe and do something else with the data.

Bishop answered 21/2, 2009 at 21:9 Comment(0)
C
4

You could do something like this with cout or cerr:

// open a file stream
ofstream out("filename");
// save cout's stream buffer
streambuf *sb = cout.rdbuf();
// point cout's stream buffer to that of the open file
cout.rdbuf(out.rdbuf());
// now you can print to file by writing to cout
cout << "Hello, world!";
// restore cout's buffer back
cout.rdbuf(sb);

Or, you can do that with a std::stringstream or some other class derived from std::ostream.

To redirect stdout, you'd need to reopen the file handle. This thread has some ideas of this nature.

Compressed answered 21/2, 2009 at 21:14 Comment(2)
This won't help you redirect another (spawned) program's stdout to anywhere of your liking.Cerography
The question refers to a third-party library. He didn't mention the need for redirection of another process' output.Compressed
C
2

This is what I'd do:

  1. CreatePipe().
  2. CreateProcess() with the handle from CreatePipe() used as stdout for the new process.
  3. Create a timer or a thread that calls ReadFile() on that handle every now and then and puts the data read into a text-box or whatnot.
Cerography answered 21/2, 2009 at 23:8 Comment(0)
C
2

Here we'll set a new entry point consoleMain that overrides your own one.

  1. Determine the entry point of your application. In VisualStudio, select Project Properties/Linker/Advanced/Entry Point. Let us call it defaultMain.
  2. Somewhere in your source code declare the original entry point (so we can chain to it) and the new entry point. Both must be declared extern "C" to prevent name mangling.

    extern "C"
    {
      int defaultMain (void);
      int consoleMain (void);
    }
    
  3. Implement the entry point function.

    __declspec(noinline) int consoleMain (void)
    {
      // __debugbreak(); // Break into the program right at the entry point!
      AllocConsole();    // Create a new console
      freopen("CON", "w", stdout);
      freopen("CON", "w", stderr);
      freopen("CON", "r", stdin); // Note: "r", not "w".
      return defaultMain();
    }
    
  4. Add your test code somewhere, e.g. in a button click action.

    fwprintf(stdout, L"This is a test to stdout\n");
    fwprintf(stderr, L"This is a test to stderr\n");
    cout<<"Enter an Integer Number Followed by ENTER to Continue" << endl;
    _flushall();
    int i = 0;
    int Result = wscanf( L"%d", &i);
    printf ("Read %d from console. Result = %d\n", i, Result);
    
  5. Set consoleMain as the new entry point (Project Properties/Linker/Advanced/Entry Point).
Codicil answered 26/11, 2015 at 16:2 Comment(0)
W
1

Thanks to the gamedev link in the answer by greyfade, I was able to write and test this simple piece of code

AllocConsole();

*stdout = *_tfdopen(_open_osfhandle((intptr_t) GetStdHandle(STD_OUTPUT_HANDLE), _O_WRONLY), _T("a"));
*stderr = *_tfdopen(_open_osfhandle((intptr_t) GetStdHandle(STD_ERROR_HANDLE), _O_WRONLY), _T("a"));
*stdin = *_tfdopen(_open_osfhandle((intptr_t) GetStdHandle(STD_INPUT_HANDLE), _O_WRONLY), _T("r"));


printf("A printf to stdout\n");
std::cout << "A << to std::cout\n";
std::cerr << "A << to std::cerr\n";
std::string input;
std::cin >> input;

std::cout << "value read from std::cin is " << input << std::endl;

It works and is adequate for debugging. Getting the text into a more attractive GUI element would take a bit more work.

Wobbling answered 23/2, 2009 at 1:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.