A simple class for printing trace statements while debugging in Windows just like in the Good Old Days.
This article presents a class that attaches a C++ iostream object to a Win32 console window. The window can be enabled or disabled at will and provides an excellent vehicle for dumping debug output. Now that the C++ iostreams specification is standardized, this class should work properly with all future versions of Win32 compilers.
Debugging the Old-Fashioned Way
I might be giving away some hints about my age, but my favorite debugging tool has always been some variation on a print statement. This is probably because I formed most of my debugging habits in the days before the creation of the windowed symbolic source debuggers everyone takes for granted these days. High-powered debuggers are great tools, but unfortunately, you cant always count on them being available on your target system. Even if you are able to install a debugger on all your test systems, you still have to contend with potentially different behavior in your debug builds. That bug youve been chasing for so long might not show up when optimization has been turned off!
To get around these types of problems, I have usually resorted to an unsophisticated trace function that I could turn on and off at run time. During the development process, I could quickly add trace statements in key spots and enable them later when trouble showed up. When writing console-mode batch programs with a command-line user interface, it was easy to spew a few lines of text in between prompts. Youve all seen interactive sessions with debug output like the following:
PayMaster> Open Employee.dat TRACE: fopen() returns 0x50691100 TRACE: get_index() returns OK 1534 records Paymaster>This sort of debug output is a little bit obtrusive, but all the necessary user-interface elements are still visible, so it makes for an acceptable solution.
Tracing with Windows
The last 10 years have proven to be the death of the traditional line-oriented text UI. Once users got the hang of menu-driven programs that ran in text windows, they were hooked. The subsequent takeover of the desktop by the Windows GUI simply put the final nail in the coffin. For most of these new programming environments, printing directly to the main window just wasnt an option any more.
My personal debugging techniques ran into two big problems with the Windows API: lack of a suitable output window class, and lack of a flexible print function.
Ive never understood why Microsoft doesnt have a console text window class. It would be really convenient to be able to create a small window where you could simply print text messages. Like a standard MS-DOS console, such a window would support fixed-width fonts, scroll text when it received a line feed, and maybe obey the standard ANSI escape sequences. It would be a dream come true for debug output.
Ive cobbled together versions of this sort of window from time to time for various projects, but have never been entirely satisfied with the results. Doing the job right is not a trivial project and usually ends up being a fairly significant consumer of code space and CPU time. Ive tried using variations on edit or list controls. Subclassing these controls is more efficient, but the resulting windows are usually hamstrung by a few missing capabilities.
The second problem is the lack of a suitable output routine. When tracing console-mode programs, I could use the Standard C librarys printf routine. The nice thing about using printf was that I didnt have to worry about allocating an intermediate buffer to hold my output text. But writing debug output to a window means using a function like vsprintf to format the text to a buffer, and then using a Windows function like DrawText or OutputDebugString to actually render the string to a window. Using vsprintf implies that you know in advance what your maximum buffer size is going to be. Worse yet, you dont have any way to check in advance for catastrophic buffer overruns.
Despite all this inconvenience, until recently most of my big Windows projects included a function that created and destroyed some sort of debug window, and a function or two for writing to that window. I would package these up in a single source file and provide a simple interface to them, defined in a file with a name like debug.h (see Listing 1).
Win32 and C++ Bring Big Improvements
Over the past few years, most of my Windows work has migrated to the Win32 platform, with my most frequent choice of development tools being Visual C++. Using C++ in the Win32 environment has enabled me to make a major improvement in managing debug output: the ConStream class. The ConStream class neatly solves the two problems I talked about in the previous section. First, it uses the Win32 API to create a text-mode console window for output. This text window is not a full-fledged graphics window; it instead behaves just like the window you get when you open an MS-DOS window. Second, users dont have to resort to special-purpose output functions; to format output to this window, you use the ConStream class, which is derived from standard iostream classes.
Using the ConStream class is engagingly simple. You simply create one object of the class and make it visible everywhere it is going to be used. Since the ConStream object doesnt deal with a message loop or have an associated window, you dont have to worry about whether it is owned by another window; you can simply make it a global object. At any point in your program, you can open or close the debug window by calling the Open or Close member functions. When the window is created, it will pop up and look something like that shown in Figure 1.
Writing data to the debug window is done exactly as you would expect with a C++ iostream object. For example, if you have a global ConStream object called Log, you would send debug information to it like this:
Log << "Success = " << success << "\n"; Log << "Value = " << value << "\n";If the ConStream window is closed, sending these log messages has no effect; they are simply dumped to the bit bucket. If the window is open, they are formatted and sent to the window just like you would expect. Figure 1 shows the output from the sample code above.
That means you can safely leave your debug message code turned on at all times, confident that the messages will be displayed only if you open your window. And those worries about buffer overruns evaporate, since formatting and management of output text is handled by the compilers standard library.
How ConStream Does It
The ConStream class is actually quite simple. The entire implementation consists of a single header file ConStream.hpp and a corresponding C++ source file ConStream.cpp. These are shown in Listings 2 and 3, respectively. As you can see in the listing, the code is compiled slightly differently depending on whether the _UNICODE macro is turned on. When this macro is turned on for a Windows 2000 or NT target, the debug window will display wide characters of type wchar_t instead of the standard char type. The ease with which this is accomplished is a testament to the design of the C++ Standard library.
The most important task for ConStream is to format data that is sent to it via the << insertion operator. Naturally, it is desirable to take advantage of the formatting already provided by iostreams, by inheriting the behavior from existing library classes. That is accomplished in the first lines of the class definition:
class ConStream #ifdef _UNICODE : public basic_ostream<wchar_t> #else : public basic_ostream<char> #endif {In case youre unfamiliar with basic_ostream, it is the templatized version of what you normally think of as class ostream. Fairly late in the standardization process, the ISO C++ committee made the brave decision to make the I/O classes in the library template classes based on the character type. While this made a lot of library code (although not application code) obsolete, it meant that the I/O classes werent tied to any particular character type, which is a very good thing.
Inheriting from the basic_ostream<T> class is well and good, but when you insert characters into a ConStream object, who decides where they go? There are a number of ways to direct stream data, but ConStream does it by attaching itself to old-fashioned FILE objects from the stdlib.h [1] portion of the C++ Standard library.
To see this attachment process in action, examine the source for the Open function shown in Listing 3. First, Open creates a console window, by calling the Windows API function AllocConsole. Next, Open obtains an OS handle for the console window, which is then converted to a low-level (stdlib.h) handle with the non-standard _open_osfhandle function call. That handle is then converted to a FILE pointer with another non-standard library call [2]: _fdopen.
The good news is that after that long string of conversions, Open finally has something that can be used to initialize an iostream object. The next line of code creates a basic_filebuf object, which is initialized with a FILE pointer. This particular filebuf constructor is one that provides some glue between the old C Standard library and the new C++ iostreams library.
The last step is to call the init function (a member of the basic_ostream<T> base class) with the basic_filebuf object as an argument. After all that work, any data inserted into the ConStream object will now be printed in the console window! The final call to setf (also a member of basic_ostream<T>) sets the stream to automatically flush after every line is inserted.
When the ConStream object is first constructed, or after the Close member function is called, there is no console window. In those cases, ConStream uses a similar process to direct the stream to the bit bucket, by calling fopen on NUL, which is the null output device on all Win32 operating systems. The code that does this is a little bit simpler, because it can skip the work needed to convert the console handle into a FILE pointer. Instead, the file pointer can be obtained directly from the call to fopen. You can see how this is done by examining the ConStream constructor at the top of Listing 3.
A Sample Program
Listing 4, ConStreamDemoDlg.cpp, shows a very small sample program that demonstrates use of the ConStream class with Visual C++ 6.0. Note that the code also works properly with Visual C++ 5.0, which implemented much of the C++ Standard library. Debug statements are scattered around the program in a few key places, including in the handlers for gaining and losing focus in the edit control and in the handler for the Display button. The behavior of this simple dialog box based program is shown in Figure 2. The dialog has a single check box that can be used to create and destroy the debug window. The code in this simple demo program shows how simple it is to write new data to the ConStream window. Its definitely a step up from using OutputDebugString [3]!
If you use Windows 2000 or NT, you can change the demo program to a Unicode program by simply defining the _UNICODE macro and changing the program entry point (as described in VC++ online help.)
Conclusion
There is obviously a lot you could do to spiff up this debug facility. But to me, the best thing about it is its simplicity. I really like being able to add this to a program by simply adding two files to the project and then adding a simple data object to my main window. Better yet, the simplicity ensures that I dont spend any time trying to debug my debugging code! So until Microsoft unveils some improved versions of the console window, Ill probably keep using the ConStream class as is.
Notes
[1] To be more accurate, these headers inherited from the Standard C library are now supposed to be offered by C++ compilers without .h extensions. The header stdlib.h becomes cstdlib, stdio.h becomes cstdio, etc. There are some other important issues to be aware of in the repackaging of Standard C library functions; for instance, all the names in the old Standard C library headers have now become a part of namespace std. To read more about these <cxxx> headers, see Standard C/C++: Frequently Answered Questions, by P.J. Plauger, CUJ, November 1999.
[2] These non-standard library calls are faithfully implemented in the libraries of the other major Win32 compiler vendors.
[3] For those unfamiliar with Windows programming, OutputDebugString is one of Win32s main tracing APIs.
Mark Nelson is a programmer for Cisco Systems in Richardson, Texas. He is presently working on IP Telephony projects for Cisco. You can reach Mark at markn@ieee.org or via his website at </www.dogma.net/markn>.