A C++ Debug Stream for Win32
C/C++ Users Journal
September, 2001 |
This article presents a class that attaches a C++ std::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, and compiler
vendors are implementing to the standard, 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 printf
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 can’t always count on them being present on your target system. Even if you are able to install a debugger on all of your test systems, you also have to contend with different behavior in your debug builds. That bug you’ve been working on finding for so long might not show up when optimization has been turned off!
My way around these types of problems has usually been to have an unsophisticated trace function that I could turn on and off at runtime. During the development process I can 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. You’ve all seen interactive sessions with debug output like that shown in Figure 1.
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 of 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 command line oriented user interface. 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 console just wasn’t an option any more.
The resulting migration to the Windows API exposed a couple of problems with my personal debugging techniques: lack of a suitable output window class, and lack of a flexible print function.
I’ve never understood why Microsoft doesn’t have a console text window class. It would be really convenient to be able to create a small window that you could simply print text messages to. Like a standard MS-DOS console, it 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.
However, Microsoft seems to be uninclined to help in this area, so I’ve cobbled together my own versions of this sort of window from time to time for various projects. Unfortunately, I 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. I’ve also 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. The nice thing about using
printf()
for debugging was that I didn’t have to worry about allocating an intermediate buffer
to hold my output text. But writing debug output to a Win32 window means using a function like
vsprintf()
to format the text to a buffer before using a 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 don’t have any
way to check in advance for catastrophic buffer overruns.
Despite all this, until recently most of my big Windows projects had 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 had a simple interface defined in a file with a
Name like debug.h
:
Win32 and C++ bring big improvements
Over the past few years, most of my Windows work has migrated to the Win32 platform, with the
most frequent choice of a development tool being Visual C++. The combination of these two things
led to a major improvement in the way my debug output was managed: 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 session. To format output to this window, ConStream
uses the standard overloaded
output functions found in the iostreams library for output.
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 doesn’t have a
message loop or a true Window, you don’t 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 3.
Figure 3 — The Console window created by the
Open()
method
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:
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 4 — Output to the Console Window
That means you can safely leave your debug message code turned on at all times, confident that the messages will only be displayed if you open your window. And those worries about buffer overruns evaporate, since formatting and management of output text is handled by the compiler’s bulletproof standard library.
How ConStream does it
The ConStream
class is actually quite simple. The entire implementation consists of a single
header file, ConStream.h and a
corresponding C++ source file
ConStream.cpp.
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 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++ library.
The most important task for ConStream
is to format data that is sent to it using the ‘<<’
insertion operator. Naturally, we want to accomplish this by inheriting the behavior from
existing library classes. That is done in the first lines of the class definition:
You might not recognize the name of the class that ConStream
is derived from. Class
basic_ostream<T>
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 define the I/O classes in the library as 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 weren’t tied to any particular character type, which is a very good thing.
Inheriting from the basic_ostream
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 portion of the standard library.
If you examine the source to the Open()
function in ConStream.cpp, you can see that
the process works as follows. A console window is first created using the AllocConsole()
API
call. The routine then gets an O/S handle for the console window. The O/S handle 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: _fdopen()
. (Note that these non-standard library calls are faithfully
implemented in the libraries of the other major Win32 compiler vendors.)
The good news is that after that long string of conversions, we finally have something that can
be used to initialize an iostream
object. The next line of code in the library 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 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()
serves to set 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, we use a similar process to direct the stream
to the bit bucket, by calling fopen()
on “nul”, the standard null output device. The
code that does this is a little bit simpler, because we get to skip the work needed to convert
the console handle into a FILE
pointer. Instead, we get the file pointer directly from the call
to fopen()
.
A sample program
I’ve written a very small sample program that demonstrates this class using Visual C++ 5.0. The
simple dialog box based program is shown in Figure 5. The dialog has a single check box that can
create and destroy the debug window. Once the window is opened, debug statements are scattered
around the program in a few key places, including the handlers for gaining and losing focus in
the edit control, and for the Display button. If you look at the code in the main program, shown
in
ConStreamDemoDlg.cpp,
you can see how simple it is use write new data to the ConStream
window. It’s definitely a
step up from using OutputDebugString()
.
If you are an NT user, 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 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 including
two new files in my project, then adding a simple data object to my main window. Better yet, the
simplicity ensures that I don’t spend any time trying to debug my debugging code! So until
Microsoft unveils some improved versions of the console window, I’ll probably keep using the
ConStream
class as is.
Downloads
- ConStreamSource.zip. The header and implementation files that you can use to add
ConStream
to your C++ project. - ConStreamDemo.zip. The Win32 executable file that demos the class.
- ConStreamDemoSource.zip. The source code for the demo program so that you can tinker with it.