Home Catalog Evaluate Download

Modifications for Charles Petzold's

Example Programs That Allow Them To Be

Single-Stepped In A Debugger

The most widely recommended book for learning to write Windows programs is Charles Petzold's Programming Windows Fifth Edition. This book includes 145 example programs written in the C language. I distribute from this web site an automated installation program called PetzoldConvert.exe which installs Gnu style makefiles which allow these 145 example programs to be compiled with the Gnu compiler and debugged with the Gnu debugger.

Debugging any Windows program can be difficult. This is a consequence of the architecture that all Windows programs are forced to adopt: the program does not repaint its window when it feels like it but rather when it is commanded to repaint via receipt of a WM_PAINT message from the operating system. The program cannot know when to expect these WM_PAINT messages because that varies depending upon the actions of the operator. The program has no knowledge of the moments when the operator will obscure the program's window with another program or choose to minimize the program's window, stretch the program's window, etc.

Therefore all Windows programs wait for receipt of the WM_PAINT message to repaint their window. If the program is prevented from receiving and responding to the WM_PAINT messages then the program's window will have visual errors (artifacts). One situation that prevents the program from responding to the stream of WM_PAINT (and all other) messages is if a program is being single-stepped within a debugger.

Professional Windows programmers use special procedures to debug their programs. One method is to employ two computers connected by a serial port cable and special software. The program being debugged is running on one computer and the debugger is running on the other computer. Another common technique is to employ a dual-headed display which means that the video card is capable of driving two different monitors at the same time. The program being debugged is visible on one monitor (CRT) and the debugger is visible on the other monitor.

Both of these techniques prevent the debugger's window from interfering with the program being debugged. When both programs are running on the same monitor, before you can issue a new debugger command (such as the single-step command) you must activate the debugger. If the debugger's window at all overlaps the window of the program being debugged then the activation of the debugger obscures part of the other window. And this other window will not get repainted until the program being debugged is allowed to respond to a WM_PAINT message, which may not happen for quite some time if the program is being single-stepped. See the problem?

But this problem only exists if the debugger's window and the window of the program being debugged overlap. Therefore you can successfully single-step a Windows program with a debugger if you can just guarantee that the two windows do not overlap.

One of the nice features of the example C++ programs that I distribute with my CPPIDE integrated development environment, is that they will automatically size themselves so as to not overlap with the debugger when you are running these programs inside the Gnu GDB debugger. This allows you to single-step the programs and not suffer from the visual artifacts that make it hard to understand where your program is and what it is doing.

Petzold's programs do not offer this feature. This means that when you debug one of Petzold's programs, it will not automatically size itself so that it does not overlap the debugger window. You will have to accomplish this yourself by dragging on the window's title bar and border. Unfortunately, if you are single-stepping the program then it won't have an opportunity to respond to this type of mouse input.

So the best solution is to convince the program being debugged to size and position itself correctly without needing any operator intervention. This happens automatically in all of the example programs that I distribute and I will now show you how to make the modifications in Petzold's programs that allow them to be successfully single-stepped with the Gnu debugger that I employ in my CPPIDE integrated development environment for C and C++ programs.

If you look at a typical Petzold example program, you will find the following section of code near the start of the WinMain() function:

hwnd = CreateWindow (szAppName, TEXT ("Analog Clock"),
                     WS_OVERLAPPEDWINDOW,
                     CW_USEDEFAULT, CW_USEDEFAULT,
                     CW_USEDEFAULT, CW_USEDEFAULT,
                     NULL, NULL, hInstance, NULL) ;

The code snippet seen above was taken from the Clock example program found in Petzold's Chapter 8. The 4 instances of the CW_USEDEFAULT keyword instruct the window to have whatever size and whatever position the operating system feels is best. You have probably noticed that the operating system likes to have the windows all be the same size and to cascade them diagonally down the screen.

We need to take control of the size and placement of the window. This can be accomplished by changing the call to CreateWindow() to look like:

// We now want to check if the command line specified any
// certain window size and placement.

UINT       x  = CW_USEDEFAULT;
UINT       y  = CW_USEDEFAULT;
UINT       cx = CW_USEDEFAULT;
UINT       cy = CW_USEDEFAULT;

ReadGeometrySwitches( &x, &y, &cx, &cy );

hwnd = CreateWindow (szAppName, TEXT ("Analog Clock"),
                     WS_OVERLAPPEDWINDOW,
                     x, y,
                     cx, cy,
                     NULL, NULL, hInstance, NULL) ;

The variables x and y describe the location of the upper left corner of the window while the variables cx and cy describe the width and height of the window. If the new ReadGeometrySwitches() function does not modify the x, y, cx, and cy variables, then this code will still function exactly like Petzold's original. However the new code gives the opportunity for some external agent to instruct the window as to an appropriate size and position. This external agent is my CPPIDE integrated development environment when it commands the Gnu debugger to take control of the program being debugged.

Obviously, you are going to have to supply this ReadGeometrySwitches() function. I show it below, along with its 2 helper functions named FindOneOf() and GetUINT(), which employ the 2 preprocessor macros named IS_NUM() and IS_SPACE(). You do not need to understand how this code works to employ it. All you need to do is to insert the following code just above the WinMain() function of whatever Petzold program you wish to single-step. Use conventional cut and paste to accomplish this.

#define IS_NUM(c)     ( (c) >= _T('0') && (c) <= _T('9') )
#define IS_SPACE(c)   ( (c) == _T(' ') || (c) == _T('\r') || (c) == _T('\n') || (c) == _T('\t') )


LPCTSTR FindOneOf( LPCTSTR p1, LPCTSTR p2 )
{
    while (*p1 != (TCHAR)0 )
    {
        LPCTSTR p = p2;
        while ( *p != (TCHAR)0 )
        {
            if ( *p1 == *p++ )
                return p1+1;
        }
        p1++;
    }
    return NULL;
}


UINT GetUINT( LPCTSTR *p, UINT uiDefault )
{
    UINT  i=0;

    while ( IS_SPACE( **p ) )
        (*p)++;

    if ( ! IS_NUM( **p ) )
        return uiDefault;

    while ( IS_NUM( **p ) )
        i = i*10 + *(*p)++ - '0';

    while ( IS_SPACE( **p ) )
        (*p)++;

    return i;

}


BOOL ReadGeometrySwitches( UINT * puiX, UINT * puiY,
                           UINT * puiWidth, UINT * puiHeight )
{
    LPCTSTR    pchCmd = GetCommandLine();

    TCHAR      szTokens[] = _T( "-/" );

    LPCTSTR    pchToken = FindOneOf( pchCmd, szTokens );

    *puiX = CW_USEDEFAULT;
    *puiY = CW_USEDEFAULT;
    *puiWidth = CW_USEDEFAULT;
    *puiHeight = CW_USEDEFAULT;

    while ( pchToken != NULL )
    {
        if ( _tcsnicmp( pchToken, _T( "x" ), 1 ) == 0 )
        {
            pchToken++;

            while ( IS_SPACE(*pchToken) )
                pchToken++;

            *puiX = GetUINT( &pchToken, CW_USEDEFAULT );
        }
        else if ( _tcsnicmp( pchToken, _T( "y" ), 1 ) == 0 )
        {
            pchToken++;

            while ( IS_SPACE(*pchToken) )
                pchToken++;

            *puiY = GetUINT( &pchToken, CW_USEDEFAULT );
        }
        else if ( _tcsnicmp( pchToken, _T( "w" ), 1 ) == 0 )
        {
            pchToken++;

            while ( IS_SPACE(*pchToken) )
                pchToken++;

            *puiWidth = GetUINT( &pchToken, CW_USEDEFAULT );
        }
        else if ( _tcsnicmp( pchToken, _T( "h" ), 1 ) == 0 )
        {
            pchToken++;

            while ( IS_SPACE(*pchToken) )
                pchToken++;

            *puiHeight = GetUINT( &pchToken, CW_USEDEFAULT );
        }

        pchToken = FindOneOf( pchToken, szTokens );

    }

    return TRUE;
}

Finally, you will need to add a #include <tchar.h> statement below the #include <windows.h> statement that you find at the very top of the file holding the WinMain() function. Only a very few of Petzold's program's already contain the necessary statement, shown below:

#include <windows.h>
#include <tchar.h>

To summarize, you only need to make these modifications if you want to single-step one of Petzold's example programs using the Gnu GDB debugger that I distribute with my CPPIDE integrated development environment. These modifications are not necessary merely to compile and run the programs (outside of the debugger). You learn quite a bit just by running and executing the Petzold programs. But you can learn even more about the nature of Windows programs and their interaction with the Windows operating system by observing the consequence of each and every individual statement by single-stepping the programs in the debugger.


Return to the Petzold Discussion Page


Return to the Catalog of Programs