Windows Programming


cout and cerr for Windows

David Singleton, BA, MSc, CEng, MIEE


David Singleton is a Director and Principal Consultant with Singleton Systems Ltd, an independent computer systems consulting company. He has a BA in Physics and an MSc in Computer Science. He is a Chartered Engineer and a Member of the Institute of Electrical Engineers. He can be contacted at his home and business address: 30 The Albany, Sunset Avenue, Woodford Green, Essex, IG8 0TJ, England. His telephone number is +44-81-505-7996.

Introduction

When developing C++ programs to run under MS-DOS, I frequently find it useful to insert writes to cout or cerr for debugging and trace purposes. For example:

int some_variable;
...
// some code setting some_variable
...
cout << "some_variable = "
   << some_variable << endl;
...
I could use the debugger, but I find that the cout approach is easier in quite a lot of cases. Once the code is working, I then remove these calls.

However, this approach does not work when developing programs to work under Microsoft Windows. Due to its totally different approach to screen-based I/O, Windows does not inherently support the standard C++ iostream class variables cin, cout, cerr, or clog. (QuickWin does support standard C++ iostreams under Windows, but does not provide full Windows capability.) The alternatives I considered were:

I decided that the first approach was too clumsy for my liking, and the second approach would involve too much work. I therefore implemented the third approach of providing cout and cerr under Windows.

I felt that my Windows implementation of cout and cerr should provide a single, separate window to which the cout/cerr stream would be directed. It should be possible to keep more than a single, windowful of output, and it should be possible to scroll forwards and backwards through the output.

My development environment for C++ uses Microsoft's C++ V7.0, together with the C++ Microsoft Foundation Classes (MFC) V1.00. Thus, the program described in the following sections uses classes derived from these MFC classes, described in full in the relevant Microsoft documentation (see References 1 and 2).

New Classes

I implemented the cout/cerr screen by deriving three classes and associated methods (C++ functions). The first class, CStrWnd, is a derivation of the standard Microsoft C++ Foundation Class CMDIChildWnd. This class supports an in-memory text buffer and the necessary user interface to scroll forwards and backwards through the text buffer. It has other, more general uses. This is why I implemented it separately from the second class, ostreamWnd.

The class ostreamWnd is a derivation of CStrWnd, tailored to interface to the third new class, winstreambuf. This, latter class is a derivation of the standard Microsoft iostream class streambuf. If one wishes to implement new iostream features, then the recommended approach is usually to derive a new class from streambuf (see References 1 and 2).

The Class CStrWnd

Listing 1, the CStrWnd header, sets out the content of CStrWnd. The most important item in class CStrWnd is the CStringArray text_buffer. This holds the text that is to be displayed in the cout window. I chose a CStringArray to hold the text, as it provided all of the functionality that I needed for handling lines of text. Each line of text to be displayed is a separate CString in the array. CStringArray also provides all the functionality needed for adding new lines and deleting old lines (once the buffer is full). text_buffer is declared as a protected member, so that derived classes can access it directly.

The constructor CStrWnd::CStrWnd loads the keyboard accelerator table for the window (required to allow multiple key combinations, such as Ctrl-Home and keyboard scrolling) and creates the window. Finally, the constructor initializes class variables and claims the input focus for the window.

Four functions, OnPaint, OnVScroll, OnHScroll, and OnKeyDown handle the basic screen output and window scrolling. SetVerticalScroll assists vertical scrolling by calculating the position of the vertical scroll bar. Unlike all the other functions, it is declared as a private function, as it is only called by other CStrWnd functions. Four functions, OnDocStart, OnDocEnd, OnLineRight, and OnLineLeft, provide special handling for the keyboard cases Ctrl-Home, Ctrl-End, Ctrl-Right, and Ctrl-Left. Finally, BufAddAtEnd provides a simple interface for adding a line to the end of the screen buffer, and UpdateScreen provides a simple interface to derived classes to request a screen update, with the last line in text_buffer displayed on the screen.

CStrWnd has five private variables, used for managing the displayed text. Listing 1 describes these variables. Listing 2 sets out the code for the CStrWnd functions. The following notes provide amplification of these functions.

MFC provides a basic windows class CWnd. This includes the standard virtual functions OnPaint, OnVScroll, OnHScroll, and OnKeyDown. CStrWnd needs to override the definitions of these functions to handle window painting, and window scrolling via scroll bar controls and the keyboard.

I designed CStrWnd::OnPaint for ease of coding and understanding rather than for efficiency. It therefore takes no account of which part of the window needs updating. If any part of the window needs updating, it repaints it all. This does result in a degree of flicker on the screen, particularly if the output is via cerr (which is unit buffered and thus causes a screen update after every insertion).

CStrWnd::OnPaint contains the functionality required to output the text. First, it selects a fixed-pitch font (which I prefer for this sort of use. Then, it calculates the height of the window. As the window could have been resized, this must be done every time. OnPaint then works out where to put tab stops, if these appear in the text. I could have put the tab-stop code in the constructor CStrWnd::CStrWnd, so that it was only called once, on creation, but it was just as easy to put it in OnPaint and the overhead is fairly low. OnPaint then gets down to its main task of outputting each of the lines of text to appear in the window. Before each line is output, OnPaint passes it to strdetab to convert all tab characters to spaces. Listing 3 and Listing 4 describe the operation of strdetab. Finally, OnPaint sets the horizontal scroll bar, if necessary.

CStrWnd::OnVScroll and CStrWnd::OnHScroll carry out scrolling of the window, either when called as a result of keyboard input or when called directly by Windows when the scroll bar controls are used. CStrWnd::OnKeyDown handles single-key keyboard scrolling inputs. It determines which key has been pressed and then calls OnVScroll or OnHScroll with the appropriate scrolling parameter.

The Class ostreamWnd

ostreamWnd is a derivation of CStrWnd. Listing 5 shows the header file that sets out its content. CStrWnd provides the capability to output text from its buffer. ostreamWnd provides the complementary methods for entering text into the CStrWnd buffer.

The constructor ostreamWnd::ostreamWnd sets the maximum size of the buffer and removes the option to close the window from its system menu. I did this to avoid any possible unpleasant side effects that might occur if the window was closed while cout and cerr were still open. As these streams are open for the life of the program, the window needs to have the same life. For this reason, I also define a null OnClose function, which prevents the window from being closed sooner than the end of the program.

ostreamWnd has two functions, SetBufferLines and PutText. SetBufferLines provides a method of adjusting the number of lines of text in the buffer, although this example does not use it. PutText takes an array of text and appends it to the last CString in the buffer. Whenever it finds a newline character '\n', it adds a new CString to the end of buffer, representing a new line to be output. After PutText has added all the text, it removes lines from the start of the buffer if the buffer contains more than the maximum permitted number of lines. Listing 6 gives the code for PutText.

ostreamWnd::PutText illustrates the benefits of using the MFC classes. By using CStringArray to hold the text buffer, I can temporarily allow the buffer to exceed its design limits perfectly safely (as when I am inserting the new text, but before I delete lines from the start), without having to worry about the code to manage this. CStringArray and CString do all the work for me. My productivity is enhanced. Further, by using SetBufferLines I could, in another program, change the number of lines in the text buffer dynamically.

The Class winstreambuf

As I mentioned earlier, the recommended method of extending the iostream classes (including cout and cerr) is to derive a new class from the standard streambuf class (see References 1 and 2). winstreambuf is such a derivation.

Listing 7 sets out the definition of the class. It overrides three of the standard, pure virtual functions in streambuf:sync, overflow, and underflow. These functions must be overridden in any class derived from streambuf. winstreambuf provides two additional functions: set_stream_window and reset_buffer. set_stream_window provides a safe interface through which users can associate a window with a winstreambuf object. reset_buffer resets the buffer pointers provided by streambuf. winstreambuf also includes two data items. The iostream system uses buffer for buffering output, and stream_window points to the ostreamWnd to be used for output.

The underlying iostream system calls sync whenever it wishes to flush the winstreambuf buffer. sync asks the associated ostreamWnd object to write the text to the window by calling its PutText function, then resets the buffer for future use.

Similarly, the underlying iostream system calls overflow whenever the buffer is full. The operation of overflow is the same as that of sync, with the addition that it needs to handle the character that caused overflow to be called.

underflow is never called here, as it is only used for input. Nevertheless, it must be defined since streambuf defines it as a pure virtual function. It always returns EOF to indicate that no characters are available for input.

Listing 8 gives the code for these functions.

A Sample Program

The previous section has described the three classes that are needed to implement cout and cerr in a Windows environment. I will now describe the additional code which, together with the classes, makes a short program that demonstrates the use of cout and cerr. The remainder of the program consists of four files.

First, Listing 9 defines a number of resource constants used within the program, for Windows message passing and dialogue management. Second, Listing 10 is the program resource file, which defines the menus and keyboard accelerator tables that the program uses. There are two menus, Run for running and exiting the program, and About to provide a standard "About" dialogue. The two accelerator tables support F2, for displaying text in the cout/cerr window, and other keys for scrolling the cout/cerr window. The file also calls up an icon, which I created, to represent the program on the Windows Program Manager window. You should use the Image Editor (supplied with the Microsoft Windows Software Development Kit) or an equivalent icon editor to create a suitable icon. Alternatively, you can delete the line. In this case, Program Manager will assign a default icon.

Listing 11 and Listing 12 set out the program for testing the cout/cerr window. The standard method of creating an application with MFC is to start by deriving a class from CWin-App. I have derived App for this purpose. InitInstance carries out initialization, and ExitInstance cleans up on termination. App defines two winstreambuf pointers, cerrbuf and coutbuf. InitInstance assigns winstreambuf objects to them and assigns them to cerr and cout. InitInstance then creates a new ostreamWnd and uses ostreamWnd::set_stream window to connect cerrbuf and coutbuf to the new ostreamWnd. cout and cerr are now ready for use. InitInstance also includes some additional debugging code that I will come to later.

A small word of warning at this point. You will notice that I do not declare cout and cerr at any time. This is because they are already declared in my copy of the standard include file <ostream.h> in a #ifndef block, which declares them unless the identifier _WINDLL is defined. I believe that this is (dare I say it) an error in <ostream.h>. It is possible that you may have a different version of <ostream.h> that uses the identifier _WINDOWS instead of _WINDLL in this #ifndef block. If this is the case, you will need to declare cout and cerr at global level in your program, as:

ostream_withassign cout, cerr;
in Listing 12, and make appropriate external declarations in other modules.

Memory Leakage Detection

One very well known problem with most C and C++ programs is that of memory leakage. I decided to make use of the facilities provided by the MFC class CMemoryState. This class is only defined in the debug versions of the MFC libraries. Hence, all references to it need to be surrounded as in:

#if defined (_DEBUG)
I declare three CMemoryState objects in App. InitInstance uses the first of these to take a memory snapshot during initialization. I take a second snapshot in ExitInstance, during close-down. I use the CMemoryState::Difference function to see whether there has been any memory leakage. If there has been leakage, then I use further CMemoryState functions to output details of the leakage. This has proved invaluable during development. To give me a simple alert, I set the program exit code to 100 (but only if it has not already been set to a non-zero value). If the exit code is not 100, then I do not need to worry about leakage.

As CMemoryState normally puts its output to the AUX device when running under Windows, the simplest way of seeing the output is to run the program under CodeView for Windows. The exit code and any CMemoryState output appear in the command window. Reference 3, supplied with Microsoft C++, provides further details on ensuring that the output is indeed directed to the CodeView command window. As cout and cerr are declared globally, it is necessary to carry out the manipulation of the associated winstreambuf objects outside the leakage detection loop. If this is not done, you get false alarms.

Further Enhancements

clog

clog is an additional output stream object. I would only need to add two or three lines to the App class to implement it.

Output Alerting

It might be useful to have the cout/cerr window brought to the front of all of the windows used by the application, whenever output is written to the cout/cerr window. This would be a fairly simple change. OnPaint could make a call to BringWindowToTop to do this.

OnPaint Improvements

As I noted above, CStrWnd::OnPaint is rather crude, in that it always repaints the entire window, regardless of what has actually changed. I would improve this so that only the parts of the window that had changed were updated. This would require changes to those routines that affected the screen in any way. Thus, I would need to change OnPaint, OnHScroll, OnVScroll, OnDocStart, and OnDocEnd among others. I would probably also need to change ostreamWnd, so that it signalled which line(s) it had changed. As they say in all the good text books, "This is left as an exercise for the reader." Good Luck.

References

1. Microsoft Corp. Microsoft C/C++ Version 7.0 Class Libraries User's Guide.

2. Microsoft Corp. Microsoft C/C++ Version 7.0 Class Libraries Reference.

3. MFC TN007.TXT. File supplied with Microsoft C/C++.

Listing 13