Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Debugging Windows Applications


NOV93: Debugging Windows Applications

Debugging Windows Applications

When the journey is not the reward.

Ray Valdes

Ray is a DDJ senior technical editor and can be contacted at [email protected].


Writing software is often viewed as a creative act, akin to painting a picture or gourmet cooking. If so, then debugging a program often seems like cleaning the paintbrushes or washing the dishes. But this doesn't always have to be the case.

Over the years, I've adopted a variety of useful approaches to debugging Windows apps, techniques I'll share with you in this article. I'll also examine tools such as Nu-Mega Technologies' Bounds-Checker for Windows, Periscope's WinScope, and Avanti's PinPoint. Accompanying this article is C code that implements a simple, relatively transparent, trace facility you can add to your Windows programs. Finally, I'll cover some truly useful debugging "tools" that aren't software but are as effective as any program I've used.

Debuggers as Commodity Items

Choosing a debugger or other development tool used to be a decision fraught with anxiety over making the wrong choice and possibly wasting significant amounts of money and time. And, as we all know, there are as many styles of debugging as coding. Debugging (and coding) styles seem to follow the "baby duck" syndrome: You get imprinted on the first viable way of doing things and find it hard to change later, even if your chosen approach is less than optimal.

One benefit of the current huge installed base of Windows is that the development-tools market now offers more value to the programmer/consumer than ever before. Language vendors (Borland, Microsoft, and Zortech among others) include powerful debuggers with their compiler packages. Even low-end products, such as Quick-C for Windows or Turbo-C++ for Windows, sport whizzy interactive development environments (IDEs) that offer source-level GUI debuggers we used to could only fantasize about. And those of us who lusted after the $2000 hardware-assisted Periscope debugger can now get something almost as powerful for one-fifth the price (Nu-Mega's Soft-ICE, no hardware required).

For a long time, Borland's Turbo Debugger was the debugging tool of choice for programmers in the trenches (although MultiScope served as a dark-horse alternative). In recent years the race among mainstream debuggers has evened up somewhat, with the newest release of CodeView moving up in the pack. Moreover, low-cost upgrades from the various language vendors make your purchase decision less of an "either/or" choice. With Turbo-C++ for Windows Visual Edition selling for $75.00, and Microsoft's Visual C++ for $139.00, there's no reason not to have multiple tools in your arsenal. Debuggers have almost become commodity items, with comparable sets of features and more-or-less equivalent user interfaces that allow you to transition back and forth among different products, as long as you don't get too deeply involved in customized scripts and configurations.

For those who are feature-hounds, however, there's much to consider, and certain debuggers, such as Soft-ICE for Windows, do stand out as unparalleled tools for a particular purpose (such as debugging device drivers and system-level programs). While detailed coverage of debugger features is beyond the scope of this article, both Arthur English's book Advanced Tools for Windows Developers (Sybex, 1993) and Charles Mirho's "Putting Four Debuggers for Windows through Their Paces" (Microsoft Systems Journal, April 1993) provide in-depth information. English devotes 120 pages to detailed descriptions of Turbo Debugger, MultiScope, CodeView for Windows, and the debugger in QuickC for Windows, while Mirho's 20-page article includes coverage of Nu-Mega's Soft-ICE for Windows.

Yet even with such a cornucopia of software technology, I've found the half-life of debugging tools installed on my disk to be quite short. I put each one on for a while and then reallocate its storage. The tools go back on the shelf, mostly unused, for a number of reasons, not just the baby-duck syndrome.

The Minimalist Curmudgeon

My first Windows project in 1986 involved four programmers adding some 30,000 lines of code to an existing 100,000-line program. At the time, we had two choices in debugging tools: Use WDEB, which was the cumbersome machine-language debugger from Microsoft, or roll our own alternative. Some of the programmers on the team attacked bugs via what I call the "Starship Enterprise" approach: a command center with two monitors, large desktop machine, and reams of printout. I used the "traveling light" approach, a quickly-written simple trace facility that displayed messages in a child window on the screen; see Figure 1. Depending on what kind of bug you're looking for, either approach has advantages and drawbacks. My approach lent itself to application-level rather than system-level debugging, and also constituted more of a platform-independent, product-independent approach. Since that time, I've reused portions of this code on application programs running on the Macintosh, DOS, UNIX, and VAX/VMS platforms. The other approach can be invaluable in tracking low-level bugs, especially those within Windows itself, or in large bodies of code that you have not written.

My trace facility consists of about 800 lines of C Code (dbgtrace.c), plus a small header file (dbgtrace.h). The debug module gets compiled and linked with your application. The listings are not shown here but are available electronically; see "Availability," page 3. The package includes an example program that shows how to trace your application via calls to the dbg_Trace() function. First, of course, you must register the debug window by calling dbg_RegisterDebugWindow(), and then instantiate it, via dbg_CreateDebugWindow(). Other than that, you can insert dbg_Trace() calls freely into the program. You don't need to remove these calls for your release (non-debug) version, nor bracket them with #ifdefs; the preprocessor will instantiate the calls as specified by a compile-time #define. If the flag is not #defined, the header file turns dbg_Trace() and the other trace functions into null statements which do not get compiled. The most complex routine, fmtStr(), is based on code I did not write originally. This routine is a general-purpose string formatter based on code written by Mike Geary and posted to Compuserve in 1986. It provides a subset of printf() functionality, and does not know how to display floating-point values. At the time I created my trace facility, Windows did not allow printf; now, you can use the Windows-compatible wsprintf(). I've not bothered to do so. Figure 1 shows a simple text editing application being traced. This example highlights a limitation of application-level tracing: It's difficult to trace a program if you can't modify the source. Here, the application relies on the edit control built into the Windows environment. However, in this case there is a workaround: By subclassing the edit control, I can intercept messages sent to its windo

w procedure with my own callback function, call the trace facility, and the call the edit control's WndProc.

When I first created it, the trace facility was rather elaborate, allowing different levels of detail, as well as menu choices to turn tracing on and off; there was also an option for writing messages to a logfile for later review. Over the years, in moving from a project on one platform to another, the code has decreased in size as I shed features that were not useful to me or difficult to maintain across multiple platforms. My debugging methodology changed from bug-finding to bug-prevention (a key point, but more on this later). And rather than keep all the multiplatform code in one module with lots of #ifdefs, there have evolved particular versions for each environment. The result is something you can implement from scratch in a few hours (or perhaps you already have!).

One programmer who has done so is Bill Rytand of Avanti Software (Palo Alto, California), author of PinPoint, a trace utility for Windows. Bill isn't alone. Programmers have come up to him at trade shows telling him they are kicking themselves for not bringing a similar product to market. Actually, his product has not yet been released, and at this writing is only available in beta form. I therefore won't say too much about it, except that it seems to provide a full-featured implementation of intrusive, application-level, tracing. There is a small API which you invoke from your application, to display various kinds of messages or to open/close a logfile. Messages can go either to a run-time window in a separate application (the PinPoint Analyzer) or to a logfile for later review by the Analyzer. If you correctly place trace statements at the beginning and end of your functions, the Analyzer can display an outline-like trace that is indented according to the depth of calls. In practice, I found using the PinPoint facility to be more cumbersome than my dirt-simple facility, but it certainly has more features, including support for programs written in C++ and Visual Basic.

Ivan Gerencir has written another trace utility; see the accompanying textbox entitled "A Multi-app Message Trace Facility for Windows." Ivan's tracer is much more sophisticated than my own. It's a standalone application that can display messages sent from multiple applications. In addition, messages can be saved to a file and printed. Unlike my generic C code, Ivan's program is written in C++ and requires Borland's OWL application framework.

Debuggers Considered Harmful

Since so many other programmers seem to revel in the technology-intensive approach to debugging--always seeking out the latest winner in the feature wars--at times I wondered if my technology-averse approach was oddball. Now I don't think so. In The Art of Computer Programming, Volume 1, Donald Knuth writes: "The most effective debugging techniques seem to be those which are designed and built into the program itself." Furthermore, Mirho states in his MSJ article that, "The advantages of run-time tracing are that it takes less time and mental effort than using a debugger, it costs nothing, and it can be controlled with environment variables."

Finally, I ran across the following passage in Steve McConnell's recently published book Code Complete: A Practical Handbook of Software Construction:

Given the enormous power offered by modern debuggers, you might be surprised that anyone would criticize them. But some of the most respected people in computer science recommend not using them. They recommend using your brain and avoiding debugging tools altogether. Their argument is that debugging tools are a crutch and that you find problems faster by thinking about them than by relying on tools. They argue that you, rather than the debugger, should mentally execute the program to flush out errors.

Of course, McConnell then goes on to say:

[This] argument against debuggers isn't valid. The fact that a tool can be misused doesn't imply that it should be rejected.... The debugger isn't a substitute for good thinking. But, in some cases, thinking isn't a substitute for a good debugger either. The most effective combination is good thinking and a good debugger.

Nu-Mega's Tour de Force

I've been using Bounds-Checker for Windows 1.02 from Nu-Mega (Nashua, New Hampshire) for the last six months on a variety of projects and have found it to be an essential tool for writing Windows programs. It's simple in concept, easy to operate, and very effective at finding errors involving memory overwrites, invalid Windows API calls, null pointers, and resource leaks. Automatic parameter validation isn't a new idea. Steve Jasik's Macintosh debugger ("The Debugger") has offered parameter checking of Toolbox calls for years. On UNIX platforms, Pure Software's Purify offers protection against memory overwrites and leakage. Bounds-Checker brings these features to Windows. The program comes on a single disk, and consumes about a half-megabyte of disk space in a single directory. As with other debuggers, there's a VxD that must be loaded via an entry in the Windows system.ini file.

Finding bugs means launching Bounds-Checker and using it to run your program. As errors are encountered, it brings up a message box informing you that some action such as a memory overwrite is going to occur; see Figure 2. If you have CodeView-or Borland-compatible debug information in the executable, a window will show you the relevant portions of your source code file. At that point you can log the error and continue, or terminate the application and go fix the bug. A minor annoyance is that there is no "Restart" option; you must relaunch Bounds-Checker to run your application again. (Because I've never had occasion to look at the manual, perhaps there is a way around this.)

Bounds-Checker isn't perfect. There are kinds of memory overwrites it can't see, those restricted to your data segment. Example 1 shows a program containing a number of errors. The comments identify whether a particular error was caught or not. For example, it knows about strcpy and will point out several instances where a string is overwritten. But if you declare the variable as a char* rather than a char[], or if you replace strcpy() with your own code, then these errors will slip by undetected. Nevertheless, there are many kinds of bugs, especially Windows-related ones, that Bounds-Checker will find. The best are those you didn't know you had.

Earlier this year, I was working on an 8000-line program that crashed about every tenth time I ran it. I could never discover the reason why--although I never sat down for a concerted effort (which might well have proved fruitless). As with many projects, the pressure to add features overwhelms the pressure to produce a bug-free program, so I added more code and the bug "went away," soon to be forgotten. When I received my copy of Bounds-Checker, I ran it on various programs I had lying around, and it zeroed in on my forgotten bug. The bug is all-too-common among Windows programmers: using a device context without doing GetDC first. Due to previous invocations to GetDC, my code happened to work most of the time, but this was a fatal error waiting to happen.

In another situation, Bounds-Checker found an error that was very difficult to find via program tracing or by stepping through the code with the Visual C++ debugger. One of my error-handling routines was occasionally doing a longjmp() using an invalid jmpbuf structure, and thereby blowing away the stack without any indication as to where the anomaly occurred. Now, when writing Windows code I make sure to use Bounds-Checker on my work-in-progress every few runs. I would do it more often but there is a small but perceptible slowdown in program execution.

Periscope's WinScope

WinScope, from Periscope (Atlanta, Georgia), is another tool for non-intrusively eavesdropping on the conversation that your application has with the Windows environment. Unlike API validators like Bounds-Checker (or SeaBreeze Software's SafeWin), WinScope is more of a passive observer--like a beefed-up version of Spy, recording its observations in a trace file which can be viewed after program execution.

The user interface consists of eight MDI child windows that display various categories of events captured by WinScope, such as messages, API calls, toolhelp notifications, Windows hooks, modules, and windows; see Figure 3. Each category can be displayed in various ways: hierarchically in outline form, alphabetically, or numerically if appropriate. The windows allow you to specify a filter for each category.

Unlike Bounds-Checker, which is oriented to testing a particular program, WinScope allows you to listen in to the crowded room that constitutes the Windows environment, in which many tasks, modules, DLLs are all conversing with each other. You'll want to set your filters judiciously, otherwise you'll be overwhelmed by the large torrent of information.

The user interface is very GUI-oriented, so much so that I had difficulty using it on my mouseless notebook computer. There are so many options that, to my taste, it ambles uncomfortably over to the Starship Enterprise console scenario. WinScope has gotten some rave reviews since its recent release and appears to be well-crafted and full-featured. Nevertheless, I found the information it gave me not particularly helpful in solving my debugging problems--too much data that I didn't need and no quick way to specify what I did need. I found the user interface, despite its GUI orientation, to be a bit hard to use without consulting the manual. Even after consulting the manual, there seems to be no single-step way to do what I want, which is to easily observe one particular message going to one particular window belonging to one particular task. For the kind of information I want to observe, I find it simpler and cheaper to insert a single trace statement in the appropriate WndProc.

However, I think WinScope has potential as an educational tool for exploring what the run-time dynamics of the Windows environment. In fact, the WinScope manual uses the subtitle: "Discovery and debugging tool for Windows application development." So if you don't share my minimalist bias, you may find WinScope to be just your ticket.

Also worth mentioning here is the upcoming Version 2 of Nu-Mega's Bounds-Checker for Windows, which is currently in beta. This adds a number of features, including a tracing facility similar to that found in WinScope, with a collapsible/expandable view of the event stream. Other features include checking API return values, the ability to view not just source code but to also browse data values at run time, and more complete coverage of the Windows API (to include "new" DLLS such as COMMDLG, TOOLHELP, OLECLI, and OLESVR).

The Secret Method for Bug-free Windows Apps

In my experience, I've found that the best way to achieve bug-free Windows apps is to not write them--Windows apps, that is. In one word: architecture. The single most important thing you can do to avoid Windows bugs is not include windows.h in most of your modules. Each time you include this header file in a module, you pay a price in program robustness and maintainability, not to mention compilation speed.

This means architecting your application so that most of the complex algorithms are found in platform-independent modules that constitute a core "engine." Of course, your engine needs to display data and interact with the outside world. This can often be done via a narrow, well-defined interface layer--a mini-API, if you will. With this architecture, you can write and debug the core on DOS or some other platform, and drive it with a stdio-based interface or some other scaffolding. If your test harness allows event logging, regression testing, and so forth, so much the better.

This methodology is essentially the one so well-articulated by Al Stevens in his article, "A Multi-tool Approach to Windows Development," in Dr. Dobb's Sourcebook of Windows Programming (Fall, 1993). Of course, if you've inherited an existing monstrous pile of spaghetti code, our nostrums will not be much help. And it's possible that you may be writing a UI-intensive application where it's impossible to extricate the core from the periphery.

The Other Secret

The other method for writing bug-free Windows apps is not to write them--bug-free apps, that is. In one word: safe coding practices, the kind your mother would teach you if she managed developers at Microsoft. I say "Microsoft" because three books have recently been published by developers who work (or have worked) at Microsoft that foster coding habits that will inoculate your apps against bugs. Given the high quality of these books, their company affiliation must be more than coincidence. Yes, I know you know Windows NT has bugs and is slow, but I don't think 4.2 million lines of code written in four years can have a prayer of working without following these guidelines.

The best of the lot is Steve Maguire's Writing Solid Code: Microsoft's Techniques for Developing Bug-Free C Programs (Microsoft Press, 1993). The others are Dave Thielen's No Bugs: Delivering Error-Free Code in C and C++ (Addison-Wesley, 1992), and the previously mentioned Code Complete: A Practical Handbook of Software Construction (Microsoft Press, 1993) by Steve McConnell.

The basic attitude is expressed in the preface to Maguire's book:

Finding and fixing bugs [should be] Development's responsibility [as opposed to that of the Testing Department].... The development teams [have] a goal of having a "nearly shippable product every day." This means that when a feature is marked complete, any bugs found in it will have to be fixed before any new work is attempted. Work in progress will be brought to a standstill if serious bugs are found in features marked complete. We [call this] attitude "zero defects."

Elsewhere, Dave Cutler at Microsoft has expressed this more colorfully: having the Windows/NT programmers "eat their own dog food every day" (doing a daily build of evolving NT system and installing it on all machines).

In his book's epilogue, Maguire writes:

You're probably wondering if I really believe it's possible to write bug-free programs. The answer is no... not absolutely. But I do believe you can come very close to writing bug-free programs, much closer than the current norm; you just have to decide to do it.

In David Thielen's book, he writes:

[It] just isn't possible to have entirely bug-free code with today's tools and technology. [But] this book focuses on teaching you how to deliver code with as few bugs in it as possible. Just as important, it also focuses on giving you the knowledge to discover what bugs still exist in the program before you ship it.

You can buy all three books for less than the cost of a whizzy but semi-useless debugging tool (or just get Maguire's for starters). If you follow the advice in them, the payoff is all but guaranteed, in my experience. The three books set down guidelines that I've been trying to follow for years. These principles are general in nature, and are not particularly tied to the Windows environment, or even the C language.

If you've been programming for awhile, you already know the basic homilies: Write clearly and simply, use meaningful variable names, use ASSERTs, use function prototypes, work with Lint, don't ignore compiler warnings, partition your datatype space with typedefs, don't mix error return values with real data, don't use unnecessary casts (they mask errors otherwise caught by the compiler), use the STRICT option in windows.h, rely on unit tests and function tests, strive for functional cohesion in your procedures, don't mistake terse source code for efficient machine code, remember that bugs don't just "go away," don't fix bugs later, fix them now, and so on.

Even so, chances are you'll find a nugget in Maguire's or Thielen's books that you may not have encountered before--or at least an interesting real-world story.

Even if you find no earth-shattering secret knowledge here, it's worthwhile to have these guidelines in hardcopy form, so you can bang your head against them when you run into a problem caused by ignoring this advice.

Maguire's writing style is fluid and clear, and the content is intended to provoke both thought and action (namely, changes in your bad habits)

Thielen's book, by contrast, is more uneven and rough around the edges, almost like a beta version. He focuses on providing C and ASM code you can use (for example, to walk the heap or check the stack), rather than highlighting problems with existing code. He also has a short chapter on commercial debugging tools.

McConnell's Code Complete shifts the focus away from bug prevention towards sound software construction and architecture. As I said earlier, your program's architecture (or lack thereof) can predetermine the success of your debugging efforts. This big book is worth a look because of its comprehensive nature, covering all phases of the development process. A full discussion of this book is beyond the scope here, but it deserves your attention.

Figure 1: A simple trace facility for debug messages.

Figure 2: Bounds-Checker in action.

Example 1: A short workout for Bounds-Checker.

Figure 3: WinScope at work.

A Multi-app Message Trace Facility for Windows

Ivan Gerencir

Ivan holds a degree in Power Electronics from the University of Belgrade, Yugoslavia, and is now in Germany developing Windows applications. He can be reached via CompuServe at 100135,1031.


Many Windows programmers use the function MessageBox() as a simple method for monitoring the behavior of a single application under development. But MessageBox() has limitations, even when used to trace just one application. As a result, I developed the program MsgDisp, which is a Windows tool for displaying informational messages from any number of running applications--all in a single widow. The messages are text strings sent over via a shared DLL.

Using MsgDisp instead of MessageBox() has several advantages. The first is that you can examine the text of your messages after your application has ended, because they are kept in a listbox control belonging to the message display task. Second, you can use MsgDisp where it would be very impractical to use MessageBox--in loops, for example. (Otherwise, you would have to keep pressing the Enter key every time your program cycles through the loop.) Also, MsgDisp enables you to save the messages received by the display task to a file, as well as letting you print them directly.

Finally, MsgDisp goes beyond single-application tracing by allowing you to monitor the behavior of cooperative processing applications (which communicate with each other using Windows messages) to reveal the exact order of processing of these messages, since MsgDisp displays messages in the order they are received.

I wrote the MsgDisp application in C++, using Borland's Object Windows Library (OWL) as provided in Borland C++ 3.1 and Application Frameworks. Listing One (page 104), excerpted source code from the MsgDisp program, is discussed in detail in the programmer's notes that, along with the complete source code, are available electronically; see "Availability," page 3. This includes a resource file, makefile, and a small test program that demonstrates the process of sending and displaying text messages.

The MsgDisp application (msgdisp.exe) is a Windows app that, once started, awaits Windows messages. Applications that wish to display trace messages do so by calling a function, also named MsgDisp, which sends text strings to msgdisp.exe. This is done via the DLL msgdispi.dll, which your application calls via the standard mechanism of an import library (msgdispi.lib) linked in with your code. The tracing function MsgDisp() is declared in the small header file msgdispi.h.

The function MsgDisp() takes one argument, which is a pointer to the string that you want displayed by msgDisp.exe. MsgDisp.exe has to be active in order for messages to be seen. However, if it is not running, this won't prevent your application from executing normally. You can therefore start the MsgDisp app at any suitable moment for debugging.

There were some interesting issues I encountered in developing MsgDisp; these are fully discussed in the programmer's notes (available electronically). For example, I use the Windows atom management functions instead of passing pointers to global memory objects. Also, instead of using FindWindow() to get the handle of the receiving window, I use HWND_BROADCAST to send a message to all top-level active windows, with a private message number allocated by the Windows function RegisterWindowMessage(). Finally, I had to dig around the OWL source code in order to discover why OWL's DispatchAMessage was not sending messages to my application.


_A MULTI-APP MESSAGE TRACE FACILITY FOR WINDOWS_
by Ivan Gerencir


[LISTING ONE]

//--------------------------------------------------------------//
// MsgDisp -- Message Text Display     (excerpted listing)      //
// Copyright (c) Ivan Gerencir, Aug 1993                        //
//--------------------------------------------------------------//

//-------------------------TMainWindow member functions-------------
#define RESOURCEID 200
TMainWindow :: TMainWindow()                        // Constructor
: TDialog(NULL, APPNAME)
{
    TextControl = new TListBox (this, RESOURCEID);  // instantiate ListBox
}
//----------------------------------------------------------------
void TMainWindow :: ~TMainWindow()                  // Destructor
{
    delete TextControl;                    // Destroy interface to ListBox
}
//----------------------------------------------------------------
void TMainWindow :: SetupWindow()
{   TDialog :: SetupWindow();
    Menu = GetMenu(HWindow);
    PopUp = TRUE;                  // PopUp menu item is initially checked
    CheckMenuItem (Menu, MENU_POPUP, MF_BYCOMMAND | MF_CHECKED);
    // Register message used for communicating with us
    if( (MsgDispMessage = RegisterWindowMessage(APPNAME)) == 0)
    {   MessageBox (HWindow,
                "Unable to register Windows Message", APPNAME, MB_OK);
        PostQuitMessage(0);
    }
}
//----------------------------------------------------------------
void TMainWindow :: DefWndProc (RTMessage Msg)
{         // Called directly from StdWndProc (owl.cpp) for messages >= 0x8000
    if(Msg.Message == MsgDispMessage)     // If we received our message
    {   if (Msg.WParam == MAGIC_ID)       // and magic data proves to be OK
        {   RecvString (Msg);             // then receive the string
            return;
        }
    }
    TDialog :: DefWndProc(Msg);
}
//----------------------------------------------------------------
void TMainWindow :: RecvString (RTMessage Msg)
{            // Client sends this message with atom number in LOWORD(LParam).
    char buffer[MAXMSGSTRLEN];
    GlobalGetAtomName (LOWORD(Msg.LParam), buffer, sizeof(buffer));
    TextControl->AddString (buffer);
    TextControl->SetSelIndex (TextControl->GetCount() - 1);
    if (PopUp)                        // If PopUp menu item checked
    {   BringWindowToTop (HWindow);
        ShowWindow (HWindow, SW_SHOWNORMAL);
    }
}
//----------------------------------------------------------------
#define REDISPLAY TRUE

void TMainWindow :: WMSize (RTMessage Msg) // Implements WM_SIZE message
{   RECT Rect;                             // Fill the whole parent window
    Rect.top = Rect.left = 0;
    Rect.right  = LOWORD(Msg.LParam);
    Rect.bottom = HIWORD(Msg.LParam);
    MoveWindow (TextControl->HWindow, Rect.left, Rect.top, Rect.right,
                                              Rect.bottom, REDISPLAY);
}
//-----------------------------------------------------------------
void TMainWindow :: CMSave (RTMessage)
{   int NumStrings = TextControl->GetCount();
    if(NumStrings == 0)
        return;
    OPENFILENAME ofn;
    static char FileBuf[256] = "";
    char FileTitleBuf[256];

    // Prepare OPENFILENAME structure for GetSaveFileName function
    memset(&ofn, 0, sizeof(ofn));
    ofn.lStructSize   = sizeof(ofn);
    ofn.hwndOwner     = HWindow;
    ofn.lpstrFilter   = APPNAME  " files (*.msg)\0"   "*.msg\0"
                                 "All files (*.*)\0"  "*.*\0";
    ofn.lpstrFile      = FileBuf;
    ofn.nMaxFile       = sizeof(FileBuf);
    ofn.lpstrFileTitle = FileTitleBuf;
    ofn.nMaxFileTitle  = sizeof(FileTitleBuf);
    ofn.lpstrTitle     = APPNAME " Save";
    ofn.lpstrDefExt    = "msg";
    ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;

    if (GetSaveFileName (&ofn) == 0)
    {   DWORD Err = CommDlgExtendedError();
        if(Err)
        {   char b[64];
            sprintf(b, APPNAME " Error %08lX", Err);
            MessageBox (HWindow, "Data not saved", b, MB_OK | MB_ICONSTOP);
        }
        return;
    }
    strcpy(FileBuf, FileTitleBuf); // Remember entered filename for next time

    FILE *f = fopen(FileBuf, "wt");    // Open file for write in text mode
    if (f == NULL)
    {   char b[256];
        sprintf (b, "Unable to open file \"%s\" for write", FileBuf);
        MessageBox (HWindow, b, APPNAME " Save data", MB_OK | MB_ICONSTOP);
        return;
    }
    // Now dump contents of ListBox into file
    for(int i = 0; i < NumStrings; ++i)
    {   char Str[MAXMSGSTRLEN];
        TextControl->GetString(Str, i);
        fputs(Str, f);
        fputs("\n", f);
    }
    fclose(f);
    MessageBox (HWindow, "Data saved", APPNAME, MB_OK);
}
//-----------------------------------------------------------------
void TMainWindow  ::  CMPrint (RTMessage)
{   int NumStrings = TextControl->GetCount();
    if(NumStrings == 0)
        return;
    PRINTDLG pd;    // Prepare PRINTDLG structure for PrintDlg function
    memset(&pd, 0, sizeof(pd));
    pd.lStructSize = sizeof(pd);
    pd.hwndOwner = HWindow;

    // Constant PD_HIDEPRINTTOFILE defined in COMMDLG.H
    // is long but doesn't end in L, so the IDE compiler
    // issues a warning.  Unfortunately, #pragma warn -cln
    // does not suppress it. Command line compiler BCC however,
    // handles it OK.
    #pragma warn -cln
    pd.Flags = PD_RETURNDC | PD_HIDEPRINTTOFILE;

    if (PrintDlg(&pd) == 0)
    {   DWORD Err = CommDlgExtendedError();
        if(Err)
        {   char b[64];
            sprintf(b, APPNAME " Error %08lX", Err);
            MessageBox (HWindow, "Printing canceled", b,
                    MB_OK | MB_ICONSTOP);
        }
        return;
    }
    // If PrintDlg allocated additional structures, release them
    if(pd.hDevMode)  {  GlobalFree(pd.hDevMode);  pd.hDevMode  = NULL; }
    if(pd.hDevNames) {  GlobalFree(pd.hDevNames); pd.hDevNames = NULL; }

    HDC DC = pd.hDC;
    POINT PageDims;         // Get physical page size in pixels
    Escape (DC, GETPHYSPAGESIZE, NULL, NULL, &PageDims);

    TEXTMETRIC tm;    // Calculate LineHeight according to the current font
    GetTextMetrics (DC, &tm);
    int LineHeight = tm.tmHeight + tm.tmExternalLeading;

    POINT PrintOffs;    // Get printing offset on the page
    Escape (DC, GETPRINTINGOFFSET, NULL, NULL, &PrintOffs);
    // Reduce vertical page size to reflect printable area,
    // for the printer cannot print on whole physical page
    PageDims.y -= 2 * PrintOffs.y + LineHeight;

    DOCINFO di;    // Prepare DOCINFO structure for StartDoc function
    di.cbSize = sizeof(di);
    di.lpszDocName = APPNAME;
    di.lpszOutput = NULL; // no redirection to file

    StartDoc (DC, &di);  // Start print job
    StartPage (DC);
    int YPos = 0;
    for(int i = 0; i < NumStrings; ++i)
    {
        if(YPos > PageDims.y) { EndPage(DC);  StartPage(DC); YPos = 0; }
        char Str[MAXMSGSTRLEN];
        TextControl->GetString (Str, i);
        TextOut (DC, PrintOffs.x, YPos, Str, strlen(Str));
        YPos += LineHeight;
    }
    EndPage (DC);
    EndDoc (DC);
    DeleteDC (DC);
}
//-----------------------------------------------------------------
void TMainWindow :: CMRepaint (RTMessage)
{                            // Redraw complete window with all the children
    RedrawWindow (HWindow, NULL, NULL,
            RDW_ERASE | RDW_FRAME | RDW_INTERNALPAINT |
            RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);
}
//----------------------------------------------------------------
void TMainWindow :: CMInsertBreak (RTMessage)
{                               // Implements Menu Options|Insert break choice
    TextControl->AddString ("======================================");
    // Force selected item to the last so that
    // it is always displayed
    TextControl->SetSelIndex (TextControl->GetCount() - 1);
}



Copyright © 1993, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.