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. std::cout.clear(); std::cerr.clear(); std::cin.clear(); std::wcout.clear(); std::wcerr.clear(); std::wcin.clear();
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).