Features


A Windows Debugging Stream

Kit Kauffmann


Kit Kauffman is a (primarily) self-taught programmer, currently specializing in C++ and Windows. He is currently working for the Salt Lake office of Sunquest, a health-care software company. You can reach him at (801) 484-4407 or (801) 277-5790, E-mail: kitk@sunquest.com.

C++ compilers currently supply only one standard class library, the iostreams package, which provides file (and string) I/O. iostreams provide an excellent example of how a well-designed set of classes can be far superior to a function library. (Please see the sidebar for a quick overview of the iostream classes if you haven't used them before.)

For my Windows debugging needs, I usually create strings (with sprintf or ostrstream), and then send the string to the Windows debug output via OutputDebugString. (I normally examine debugging output with DBWIN.EXE, a sample Microsoft SDK program which creates a standard scrolling window for the output, and allows further control over the output target.)

Sometimes, I prefer to display debugging messages via MessageBox, which, like OutputDebugString, requires strings. I prefer the ostrstream class over sprintf for creating strings, but even with ostrstream I must create repetitive sequences, such as:

char buf[200];
ostrstream s( buf, sizeof( buf ) );
s << "i = " << i;
OutputDebugStream( BUF );

// or
MessageBox( NULL, BUF, NULL, MB_OK );
I want a direct debug stream which allows constructs such as the following:

MyDebugStream s;
s << "1 = " << i;
Languages with inheritance, like C++, are powerful tools in that they enable developers to build on what already exists. Since the iostreams classes already provide most of the functionality necessary, completing a class like MyDebugStream required only a little judicious inheritance.

iostreams design separates data-type formatting from buffer management/disposition. Stream objects (such as ostreams or istreams) perform the conversion of data, placing result strings in (or getting strings from) buffers managed by streambuf objects. streambuf objects, in turn, re-fill (or empty) the buffers when required, and manage the memory, disk space, or whatever I/O medium is the given streambuf target.

The programmer may construct stream objects by passing any streambuf to a stream constructor that accepts a streambuf pointer. Obviously, connecting an istream (an input stream) to an output-only device won't accomplish much, but in general, this flexibility in constructing streambufs provides great power for the streams programmer.

Since ostream already provides the necessary programming interface, with a streambuf performing the output work, I need only derive a custom ostream which will connect to a derived streambuf. To accomplish this task, I have created the class MyDebugStream, from debug.h (Listing 1) . MyDebugStream contains only a constructor, which passes its parameters to the MyDebugStreambuf constructor (and connects to that same streambuf for output). These parameters specify whether output goes to a MessageBox or to the debug window, and specify a title for the MessageBox as well.

Since my custom streambuf must support OutputDebugString and/or MessageBox, which both require strings, and since strstreambuf already manages string I/O, I can obviously derive a custom streambuf from strstreambuf (see Listing 2) . streambuf must implement three functions: underflow, to handle empty get buffers; overflow, to handle full put buffers; and sync, to handle explicit empty/fill requests. Because a debug stream is output only, a program outputting to the debug stream will not call underflow, but it may call overflow and sync. Therefore, I must provide a function to be called by overflow and sync. This function will in turn call MessageBox or OutputDebugString; I have added this function as the Flush method. Constructor/destructor logic allocates and frees a buffer, to round out the class.

The constructor saves the parameter information, and allocates a buffer for the parental strstreambuf. The setp function tells strstreambuf where the beginning and end of the buffer are located. Normally, users of ostrstream must explicitly call the I/O manipulator ends to put a null terminator on the string. I want this class to work even if the users forget, so my code sets aside an extra character in case the buffer is full.

The overflow and sync methods are very simple. The out_waiting call indicates that data is in the buffer, so my code calls Flush, and then calls the parental overflow or sync, just to be polite. The Flush method is almost trivial; a minor complication worth noting is that the pptr method called by Flush returns a pointer to the next character to write — which is the perfect place to append a null.

Refinements

The code as I have presented it so far needs a little refinement to handle a couple of problems. For example, the debug window requires carriage returns to break up the output into lines. Also, a program can't always use MessageBox (e.g., during the processing of a WM_PAINT message). To address these problems I have added some simple I/O manipulators: BoxFlush, to put the buffer into a MessageBox; and DebugFlush, to send the convenient carriage return to the debug window. This addition requires just the two #defines and the two functions at the end of debug.h and debug.cpp (see Listing 1 and Listing 2) .

Now, I can write:

CDebugStream dbg( TRUE, "Debug Output" );
int i;
long l;

dbg << i <<" is an int!" << DebugFlush;
And the debugging message goes to the debug window, but if I then write:

dbg << l << " is a long!" << endl;
The debugging message goes to a MessageBox, as it will in this statement:

dbg << i << " is still an int!" << BoxFlush;
The debugging class makes it easy to switch back and forth between the debug window and MessageBox, and provides the less error-prone stream-style output method as well. Note that you could also easily alter or extend the streambuf class to support other types of string-oriented debugging output, such as the monochrome screen. Also, you can connect streambuf to any ostream variation with code like:

CDebugStreambuf sb( TRUE, "Title" );
YourStream s( &sb );

Conclusion

I originally felt somewhat intimidated by iostreams, and I was reluctant to try creating my own stream class. I found creating a stream class much easier than I had expected — obviously a tribute to iostreams' good design. Of course, without C++'s inheritance features, I could not have provided so much functionality with so little code.

One final note: I designed these classes around the iostreams family supplied with Microsoft C++ v8, otherwise known as Visual C++. Any iostreams family with a compatible strstreambuf should work unchanged; you should be able to easily alter this class for a different implementation as well.