If thou suffer injustice, console thyself; the true unhappiness is in doing it

This post shows you how to add a debugging console to a Windows GUI application and configure it to receive output generated by printf or cout. It also illustrates the technique of specifying a custom entry point for a program.

The console will be created before any CRT initialization or constructor calls for global C++ objects. Having the console available early can be very handy, especially when debugging.

Few Windows GUI programs create a text-based console in addition to the regular window-oriented interface. Be aware that if your app does already have a console, the following may cause the existing console creation to fail because a Windows program can have only one console.

The procedure consists of creating a new console or attaching to an existing console in another process using AttachConsole (which requires sufficient access rights). After creating or attaching to the console, the standard input/output handles, stdin, stdout, and stderr are configured to use it. This allows you to use printf or cout which is more convenient than using functions like WriteConsole directly.

By using AttachConsole you can send the output of multiple programs to a single console, or allow a sequence of nested programs to output to a console inherited from a parent process.

Specifying a Custom Program Entry Point

In order to set up the console very early in the program’s execution, we are going to specify a custom entry point. The new entry point function will create the console, configure standard I/O to use the console, then call the original entry point.

In order to call the original entry point we need to know its exact name including the leading “w” for a “wide” or Unicode build. The most reliable way to do this is to build the program before changing the entry point and use DumpBin to see what the entry point is:

  • From a command prompt, run vcvars64.bat (typically located at c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\amd64\vcvars64.bat) to set up the development environment. Make sure you use the 64-bit version if your program is 64 bits. The 32-bit version of DumpBin will report incorrect information.
  • Type “dumpbin /headers MyApp.exe” (substituting your application name).
  • Look for a line that reads something like “1121C entry point (0041121C) @ILT+535(_wWinMainCRTStartup)”. The entry point function name is in the second set of parentheses (minus the leading underscore).

You can also get the entry point from inside Visual Studio, but it is slightly more tricky because if the default entry point is used, the field is blank and you need to figure out the exact name for yourself (wMainCRTStartup, MainCRTStartup):

  • Open the Visual Studio project or solution for the program.
  • Select the program’s project from the Solution Explorer pane on the left side.
  • Open the project’s property pages by pressing Alt+F7 or select Project, then TestApp Properties from the menu.
  • In the Property Pages dialog, expand “Configuration Properties”, then “Linker”, then click “Advanced”.
  • Look for “Entry Point”. If this field is empty, then the entry point is the default: WinMainCRTStartup (ANSI) or wWinMainCRTStartup (Unicode) for a GUI application. Otherwise record the name that is there.

Declare the entry point function

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 wWinMainCRTStartup (void); // Use the name you obtained in the prior step
   int MyEntryPoint (void);

Implement the entry point function

For clarity, all error detection and handling has been removed.

__declspec(noinline) int MyEntryPoint (void)
   // __debugbreak(); // Break into the program right at the entry point!
   // Create a new console:
   freopen("CON", "w", stdout);
   freopen("CON", "w", stderr);
   freopen("CON", "r", stdin); // Note: "r", not "w".
   return wWinMainCRTStartup();

The interesting part of this function is the set of calls to freopen for “CON”. Because this is a GUI program, stdin, stdout and stderr are not initialized. We have to explicitly set them to either an open file or the console (“CON”) in order for printf, scanf, etc. to work.

stdin, stdout, and stderr are all of type FILE* which is just an opaque pointer to a descriptor for an open file. The file itself can be either a “real” file on disk (which is how redirection of stdout and stdin to/from a file is implemented) or a reserved device name such as “CON” (the console).

freopen closes the file that the current FILE* represents (here stdout, stderr, or stdin) if it’s already open (in our case it’s not open), and opens the specified file (or special device), assigning the resulting FILE descriptor to the same FILE*. This is the logical equivalent of calling fclose(stdout) followed by stdout = fopen(…). But because stdin, stdout, and stderr are special you can’t actually assign a value to them. freopen does the equivalent of such an assignment for you.

For those of you who are interested, stdout is defined in stdio.h using the preprocessor as follows (and similarly for stdin and stderr):

#define stdout (&__iob_func()[1])

This is a function call returning a pointer to an array of FILE* from which we select one element. Since the result is not an lvalue, it can’t be assigned to. freopen instead replaces an element of the array that __iob_func() returns.

All we’re doing is making stdin, stdout and stderr point to the console. You could specify the path for a “real” file instead of “CON” to cause output go to the specified file instead.

You can now do things like the following inside WinMain. Note: the scanf call will not return until an integer is entered into the console that pops up and enter is pressed. This can be used to allow entry of configuration information, such as a test number, at run time:

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;

int i = 0;
int Result = wscanf( L"%d", &i);
printf ("Read %d from console. Result = %d\n", i, Result);

The final step is to set the entry point for the program in Visual Studio to the new function. The location for this setting is the same place we went earlier to look for the original entry point (under advanced linker properties).

There you have it. A GUI program with a console that you can output to using the standard I/O functions. If you like, you can download a complete sample program and Visual Studio project.

Post a Comment