Redirecting standard output to console in Windows

When I first started making Windows programs in the early 2000s I needed a way to display logging information visually to the user. With some internet sleuthing I discovered how to create a windows console and using black magic, how to redirect the standard input/output streams to print to this window. With that problem solved I happily used that code unchanged for many years.

At some point it stopped working – I suspect when Microsoft updated their standard library implementation in a newer version of Visual Studio it caused the solution to stop working. Curious, I looked at the code for the first time in nearly a decade and recoiled in horror. The old code redirected output by mucking around with handles and copying the contents of internal FILE structs!

// Redirect unbuffered STDOUT to the console.
long standardHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE);
int consoleHandle = _open_osfhandle(standardHandle, _O_TEXT);
FILE * fp = _fdopen(consoleHandle, "w");
*stdout = *fp;
setvbuf(stdout, NULL, _IONBF, 0);

While researching a better solution to the problem of input redirection, I came across an answer on Stack Exchange. It turns out someone else was having the same problem I did, and one of the answers had the same story I did about using the code above and discovering how hacky it was years later! He gives a pretty detailed explanation for what the old code was doing, and how to use freopen to accomplish this behavior correctly.

And now for the solution! Here’s the new code I use to create a Windows console, and redirect standard input/error/output to the console:

// Create the console window and set the window title.
if (AllocConsole() == 0)
    // Handle error here. Use ::GetLastError() to get the error.

// Redirect CRT standard input, output and error handles to the console window.
FILE * pNewStdout = nullptr;
FILE * pNewStderr = nullptr;
FILE * pNewStdin = nullptr;

::freopen_s(&pNewStdout, "CONOUT$", "w", stdout);
::freopen_s(&pNewStderr, "CONOUT$", "w", stderr);
::freopen_s(&pNewStdin, "CONIN$", "r", stdin);

// Clear the error state for all of the C++ standard streams. Attempting to accessing the streams before they refer
// to a valid target causes the stream to enter an error state. Clearing the error state will fix this problem,
// which seems to occur in newer version of Visual Studio even when the console has not been read from or written
// to yet.


That should do it! Remember to properly close the file pointers before the program closes. (It’s not the end of the world if you don’t – Windows will clean it up when the program exits).