Kevin Pinkerton is a Senior System Programmer for AlliedSignal Technical Services in southern Maryland. He has been programming for sixteen years. Much of that time involved VAX/VMS systems. More recently he has been writing C+ + Windows programs and drivers for specialized data acquisition boards. He can be reached at 301-862-8862.
How often have you used output statements to try to find that elusive program bug? Carefully placed output statements can help you follow your program's execution flow or watch the state of a variable. If only they didn't tend to clutter up your screen and interfere with the program's user interface. I have developed an output stream compatible with MS-DOS and Windows that will write to a secondary monitor.
The mstream MDA Output Stream
mstream (Listing 1 and Listing 2) , the output stream class I developed based on the ostream class, inherits all the overloaded insertion operators (<<) from ostream.mstream's class constructor creates an instance of the mstreombuf class and uses it for mstream's buffer. The mstreambuf class is the heart of the MDA output stream. Each character displayed on the MDA monitor uses two bytes of video memory. The low-order byte contains the character and the high order byte contains the display attribute. The video memory is mapped directly to the monitor's display. Each character and display attribute written to video memory is immediately displayed on the MDA monitor.I also defined a global instance of the mstream class called mout. mstream's header file defines an external reference to mout. You must include the mstream header file in your program and link to the appropriate MS-DOS or Windows link libraries before you can use mout in your program. Once you have linked mstream with your program, you can use mout just as you would use the C++ cout standard output stream. You also can use the standard stream manipulators defined in iomanip.h. Here are some mout usage examples:
mout << "XMAX = "<< xmax <<" in module XYZ"; mout << "bit code =" << (hex) << bitcode << endl;An external reference to mout is included in mstream.h so that everyone can use mstream without having to create their own instance of it. The global instance in mstream's source file works well for the MS-DOS and Windows static link libraries. However, defining a global instance in the DLL and trying to get the linker to find it when you link your program won't work. (More on this later.)mstreambuf uses the screen member to point to the video memory. screen is an unsigned integer array where array position 0 points to row 1, column 1, array position 1 points to row 1, column 2, and so on. mstreambuf uses the row and column class members to form an index into the screen array. The MS-DOS version of mstreambuf creates a far pointer to memory address 0XB000 and assigns it to screen. The Windows version of the mstreambuf assigns _BOOOH, a global selector constant, to screen. The constant_BOOOH is one of several global selector constants that Windows provides for accessing common BIOS memory areas. screen, row, and column are all static class members so only one copy of each of them exists for all instances of the class. This will keep multiple instances of mstream from overwriting each other.
The mstream class constructor sets the stream's unit buffered (unitbuf) format flag. The unitbuf flag causes ostream::osfx to flush the stream after each insertion by calling streambuf::sync. In mstream's case, ostream::osfx will end up calling mstreombuf::sync. mstreambuf::sync takes each character out of the buffer and writes it (along with a display attribute byte) to the MDA video memory. The mstreambuf class also redefines the overflow virtual function. Other stream functions frequently call the overflow function when they sense that the put area is full. overflow flushes the stream and resets the put area pointers.
The Link Libraries
I compiled, linked, and tested the source code using Borland C++ v3.1. Listing 3, Listing 4, Listing 5, Listing 6, and Listing 7 show sample command procedures to compile and build large memory model versions of the MS-DOS and Windows static link libraries and the Windows dynamic link library (DLL). I prefer to use the DLL library when working with Windows. A single copy of a Windows DLL and all of its static and local variables are used by all the programs that link to it. In mstream's case, this means that multiple programs can send output statements to the MDA monitor at the same time and not overwrite each other.The linker needs to know where to find the references to the DLL routines that your program uses. The easiest way to do this is to create a special import library version of the DLL. The import library contains all the exported (callable by Windows) functions and class names in the DLL and allows the linker to construct a reference table in the .EXE file. When Windows loads the program, it uses this table to resolve calls to the DLL. Unfortunately, you cannot export any of the DLL's static variables (or in this case, class instances) into an import library. So I created a separate source file (Listing 8) that contained the mstream mout global instance statement. I then compiled it using non-DLL compiler switches and added it to the import library. This portion of the import library will be statically linked to your program. This is not a good practice if you plan on distributing your DLL, but as long as the make or batch file automates the process I feel comfortable with it. A DLL also requires two additional routines: LibMain and WEP. Windows calls LibMain when it loads the DLL into memory and WEP when it unloads it. The version of these routines used by the library are empty shells that do the minimum amount required by Windows.
Important Note: The DLL library is compiled with the DLL version of the C Runtime Library. If you use the DLL library, your program must also use the DLL version of the C runtime library. Otherwise the linker will not be able to resolve your references to mstream.
I find this MDA output stream to be an invaluable tool for debugging programs, especially Window programs. There are several enhancements that would increase its usefulness. One of them would be adding a file stream to mstream that would mirror the MDA output to a permanent log file. Another might be to allow exact row and column positioning similar to the conbuf streambuf.