Jason Doucette / Xona.com
location: Arcadia, Yarmouth, Nova Scotia, Canada
contact: or other forms of contact
social networks: facebook hi5 myspace
forum: Xona Forums
HOME | RÉSUMÉ | PROJECTS/GAMES | GFX | A.I. | TRANSCRIPTS | WORLD RECORDS | WALLPAPERS | CONTACT
PROGRAMMING WINDOWS 5TH EDITION ERRATA | DOMAIN HACKS SUGGEST | MATTHEW DOUCETTE | XONA.COM



Click to purchase Programming Windows, Fifth Edition by Charles Petzold
Click to Purchase Book
Programming Windows, Fifth Edition
by Charles Petzold - The definitive guide to the Win32® API

Errata Addendum

(Contains additional errata from Charles Petzold's and John Kopplin's errata lists,
with my own detailed explanations for completeness and to ensure understandability.
A minimal screen resolution of 1024 x 768 is recommended for proper viewing.
)
Last Update: Sunday, July 23rd, 2006

Back to Jason Doucette's Resume Page

Quick Links:
Pre Section All Sections
About Author All Chapters
Section I - The Basics
Section II - More Graphics
Chapter 1 - Getting Started
Chapter 2 - An Introduction to Unicode
Chapter 3 - Windows and Messages
Chapter 4 - An Exercise in Text Output
Chapter 5 - Basic Drawing
Chapter 6 - The Keyboard
Chapter 7 - The Mouse
Chapter 8 - The Timer
Chapter 9 - Child Window Controls
Chapter 10 - Menus and Other Resources
Chapter 11 - Dialog Boxes
Chapter 12 - The Clipboard
Chapter 13 - Using the Printer
Chapter 14 - Bitmaps and Bitblts
Chapter 15 - The Device-Independent Bitmap
Chapter 16 - The Palette Manager
Chapter 17 - Text and Fonts
Chapter 18 - Metafiles
Section III - Advanced Topics
Chapter 19 - The Multiple-Document Interface
Chapter 20 - Multitasking and Multithreading
Chapter 21 - Dynamic-Link Libraries
Chapter 22 - Sound and Music
Chapter 23 - A Taste of the Internet

[Please be patient... page is loading (270+ kb in size)...]
Pre
Section
    About Author
 
Erratum 1: Incorrect URL

On page xxiii, Author's Note, the first line states:

Page xxiii, Author's Note
Visit my web site www.cpetzold.com for updated information regarding this book, including possible bug reports and new code listings.

The domain cpetzold.com does not load, anymore. Please visit Charles Petzold's new domain at www.charlespetzold.com. Also, the email address listed on the same page is no longer valid. Please visit his new domain for an up-to-date email address. If you wish to write him concerning a problem with the book, you may also like to review the web page you are viewing right now to see if the problem has already been resolved. If not, you can contact me via the email address listed at the bottom of this page.

Credit: Jason Doucette

All
Sections
    All Chapters
 
Erratum 1: GetMessage() not checked for errors

This 'bug' regards the Message Loop in every program in the book. I should point out that Charles Petzold states for the purposes of clarity, he does not check a lot of functions for errors:

Page 57, Chapter 3, A Window of One's Own
I do a minimum of error checking in the sample programs in this book. This is not because I don't think error checking is a good idea, but because it would distract from what the programs are supposed to illustrate.

I have decided to write this in my Errata Addendum because I believe most people are not aware that GetMessage() is one of those functions that they should be testing for errors. The Message Loop is important, as it is the core of just about every Windows program created.

The Message Loop is poorly documented in the MSDN that comes with MSVC++ 6.0. For example, in /Platform SDK/Windows Programming Guidelines/Win32 Programming/A Generic Win32 Sample Application/The Entry Point Function/Entering the Message Loop it shows:

Various MSVC++ 6.0 MSDN Pages - Error
while( GetMessage( &msg, NULL, 0, 0 ) ) {
   TranslateMessage( &msg );
   DispatchMessage( &msg );
}

It shows the same thing in /Platform SDK/User Interface Services/Windowing/Messages and Message Queues/About Messages and Message Queues/Message Handling/Message Loop, as well as in /Platform SDK/User Interface Services/Windowing/Messages and Message Queues/Using Messages and Message Queues/Creating a Message Loop. These are all in contradiction to the MSVC++ 6.0 MSDN page for the GetMessage() function itself, which explains:

MSVC++ 6.0 MSDN page for GetMessage()
Note that the function return value can be nonzero, zero, or -1. Thus, you should avoid code like this:

while (GetMessage( lpMsg, hWnd, 0, 0)) ...

The possibility of a -1 return value means that such code can lead to fatal application errors.

The fact that any nonzero number is equivalent to true, means that when there is an error, the loop will not quit, but will attempt to translate and dispatch the message in msg, which probably still contains the last message information (it is undocumented what it actually contains).

A proper message loop is explained in the online MSDN page for GetMessage():

online MSDN page for GetMessage() - Correction
Because the return value can be nonzero, zero, or -1, avoid code like this:

while (GetMessage( lpMsg, hWnd, 0, 0)) ...

The possibility of a -1 return value means that such code can lead to fatal application errors. Instead, use code like this:

BOOL bRet;

while ( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

Credit: Jason Doucette

 
Erratum 2: CreateWindow() not checked for errors

This 'bug' regards the CreateWindow() call in every program in the book. Again, I should point out that Charles Petzold states for the purposes of clarity, he does not check a lot of functions for errors:

Page 57, Chapter 3, A Window of One's Own
I do a minimum of error checking in the sample programs in this book. This is not because I don't think error checking is a good idea, but because it would distract from what the programs are supposed to illustrate.

However, since the CreateWindow() call is so important, each program would be better if it checked the return value from CreateWindow() before using it in the subsequent call to ShowWindow() from within the WinMain() function.

Credit: John Kopplin

 
Erratum 3: Code Change Option

All programs whose WM_PAINT message handler redraws the entire client area (that is, in all cases of repainting, without using ROPs (raster operations) that merge with the background), do not require a background brush.

Windows automatically fills the client area with the background brush selected when a window is resized. It does so by sending a WM_ERASEBKGND Notification to the window, and the default processing of this message, via the DefWindowProc() Function, is that the background is 'erased' by using the class background brush specified by the hbrBackground member of the WNDCLASS structure. If this is NULL, the background is not erased (although the application could process the WM_ERASEBKGND Notification and erase it manually).

Thus, for programs that are going to fill the client area themselves in their WM_PAINT handler, this will result in a slower application that flickers needlessly. The flicker occurs because the application fills the client area completely immediately after Windows has just finished filling it in with the background brush.

To remove the background brush in any of these programs in the text book, change this line in WinMain():

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

to this line:

wndclass.hbrBackground = NULL ;

and the window will no longer flicker.

The following programs are known to not require a background brush:

Chapter 14:
BITBLT.C (page 649)
STRETCH.C (page 653)

Chapter 16:
GRAYS1.C (page 823)
GRAYS2.C (page 826)
GRAYS3.C (page 833)
SYSPAL2.C (page 843)
SYSPAL3.C (page 847)
PALANIM.C (page 852) (all programs that use it redraw their entire client area themselves)

Credit: Jason Doucette

 
Erratum 4: Speeding Up Build Times

As explained on the MSDN page Speeding up the Build Process, you can define the macro WIN32_LEAN_AND_MEAN to reduce the size of the Win32 header files by excluding some of the less common APIs (Application Programming Interfaces). You can define the WIN32_LEAN_AND_MEAN macro like so at the top of your program:

#define WIN32_LEAN_AND_MEAN

Almost all of Charles Petzold's examples can have this macro to speed up build times. If there are programs that report errors because they use certain functions that require headers excluded by this macro, you can either not define the macro, or define that specific header. You can find out what particular headers are required for these functions by looking at their MSDN pages. If you have a local copy of MSDN, place the cursor on the function name, and press F1 to load its MSDN page.

For example, the rand() function becomes unavailable when you define WIN32_LEAN_AND_MEAN. Looking at its MSDN page, you can see that it requires the stdlib.h header. You can include this header like so:

#include <stdlib.h>

Credit: Jason Doucette

 
Erratum 5: Security Issues

On page 36, Chapter 2, Using printf in Windows, there is a table that displays a number of different printf() style functions. One that is used very often in Charles Petzold's examples is the wsprintf() function. While his examples are just for demonstration purposes, real world applications should avoid using functions in the list that are not listed under 'Max-length Version'. Writing formatted data to a string that does not ensure data will not be written outside of a specified range can cause a buffer overflow security problem. A security note posted within wsprintf()'s MSDN page states:

"Security Alert: Using this function incorrectly can compromise the security of your application. The string returned in lpOut is not guaranteed to be NULL-terminated. Also, avoid the %s format -- it can lead to a buffer overrun. If an access violation occurs it causes a denial of service against your application. In the worse case, an attacker can inject executable code."
You should always play it safe and use the 'Max-length Version' of the function you wish. This ensures buffer overflows will never occur.

When using the 'Max-length Version' functions, please note that most, if not all, do not ensure NULL termination. I have posted an errata elsewhere in this list about the improper use of such a function in the coding example on page 37, Chapter 2.

Credit: Jason Doucette

 
Erratum 6: Simulating a Button Press Message

There are several places in the book which has code whose purpose is to emulate a button press. To do such a thing, you need two pieces of information:
  1. a handle to the button's parent window.

    This is normally a dialog box, whose handle is stored in a variable called hdlg, which means 'handle to dialog'. Some of the examples in the book create a dialog box as the program's main window. Since Charles Petzold has been consistent in that the main window's handle is stored in a variable called hwnd, some of these examples have buttons whose parent's handle is hwnd.

  2. the button's identifier.

    This is the identifier that you give the button when you create it inside of a dialog using the resource editor. For the default 'OK' and 'Cancel' buttons, they are IDOK and IDCANCEL, respectively. The examples in the book use the notation IDC_NAME, where 'IDC' means 'ID of a control', as explained on page 504, Chapter 11, A More Complex Dialog Box, and 'NAME' is whatever you prefer to call your button. These identifiers are defined in RESOURCE.H as a result of their creation in the resource editor.
Let us assume that we wish to emulate the pressing of a button whose identifier is IDC_MYBUTTON, and whose parent has a window handle stored in the variable hdlg. Charles Petzold's method of creating this event is as follows:

SendMessage(hdlg, WM_COMMAND, IDC_MYBUTTON, 0) ;
He does so in the following locations in the book:
  • Page 1012, Chapter 17, PICKFONT.C
  • Page 1013, Chapter 17, PICKFONT.C
  • Twice on page 1303, Chapter 22, RECORD1.C
  • Three times on page 1317, Chapter 22, RECORD2.C
  • Page 1318, Chapter 22, RECORD2.C
  • Twice on page 1324, Chapter 22, RECORD3.C
  • Twice on page 1412, Chapter 23, NETTIME.C
  • Twice on page 1413, Chapter 23, NETTIME.C
I should note that his code works, but it is not entirely proper. It is not good code, as it obfuscates what is actually happening. The process that this SendMessage() call is emulating is the following:

A user clicks on the button and releases it. The button handles these mouse messages in its own window procedure, and responds by sending a message to its parent. The parent, knowing the button has been clicked, can respond appropriately. The message it sends is a WM_COMMAND notification with three pieces of information passed in the wParam and lParam parameters. The information is as follows:
  1. wParam: The high-order word specifies the notification code.
  2. wParam: The low-order word specifies the identifier of the button.
  3. lParam: Handle to the button sending the message.
The notification code of a button press is BN_CLICKED, as explained on page 365, Chapter 9, The Child Talks to Its Parent. The identifier of the button, in this example, is IDC_MYBUTTON. The handle to the button sending the message can be found using the GetDlgItem() function. It takes two parameters: the handle to the dialog who is the button's parent, and the identifier of the button. Therefore, we would call it in the following manner:
HWND hctrl = GetDlgItem(hdlg, IDC_MYBUTTON);
One problem of passing three pieces of information required by the WM_COMMAND message, is that we must combine two of these pieces of information into one, for the wParam parameter. We can use the predefined MAKEWPARAM Macro for this. It takes two words as its parameters and combines them into a WPARAM type. We would use the macro as follows:
WPARAM wParam = MAKEWPARAM(IDC_MYBUTTON, BN_CLICKED);
We now have both the wParam and lParam parameters for our WM_COMMAND message that is to be sent to the dialog window. Putting this all together results in the following:
SendMessage (
	hdlg,                                 // handle to window we are sending message to
	WM_COMMAND,                           // message we are sending
	MAKEWPARAM(IDC_MYBUTTON, BN_CLICKED), // control identifier, and notification code
	GetDlgItem(hdlg, IDC_MYBUTTON));      // handle to window that is sending the message
Let us analyze Charles Petzold's method to see what is different.

Firstly, he does not combine two pieces of information into one to be passed into the wParam parameter. He simply passes the identifier of the control. You will notice that because BN_CLICKED is defined as 0, that our MAKEWPARAM call results in just being the identifier of the control. So, his code was logically correct. However, it is still bad code, as it obfuscates what is actually happening. When you look at the fixed code above, you know that you are sending a BN_CLICKED notification code to this control's parent.

Secondly, he passes NULL for the lParam parameter. As explained on the BN_CLICKED page, the lParam parameter is the handle to the button. To properly emulate a user pressing the button, everything should remain identical to the real thing.

Logically, the only difference in the code was the lack of passing the handle to the control sending the message. Since the original code works, does this really matter? It matters only to the code that handles the message. Since this code exists in your own dialog box window procedure, it is up to you whether it uses this information or not. None of the examples in the book have any reason to use such information, therefore it does not matter whatsoever, in these cases. However, I would like to stress again that it is better code to explicitly pass all information, so that you and others who read your code will understand exactly what is going on.

There is another method to simulate a user pressing a button, which is far easier than the above code. It is to use the BM_CLICK Message. As stated on its MSDN page, it simulates the user clicking a button by sending WM_LBUTTONDOWN and WM_LBUTTONUP messages in succession to the button, which will cause the button to think it has been pressed, to which it responds by sending the button's parent window a BN_CLICKED notification message exactly identical to what we have created above. You will notice that, to send this message, it only requires the handle to the button. We must use the GetDlgItem() function to find this. Thus, we can simulate a button press with the following code:
SendMessage(
	GetDlgItem(hdlg, IDC_MYBUTTON),	// handle to button to be pressed
	BM_CLICK,                       // message ID   
	0,                              // wParam = 0; not used, must be zero   
	0);                             // lParam = 0; not used, must be zero   
If this is so much simpler, why don't we use it, instead? Because, it causes a button press by emulating a mouse click. This is done by sending mouse messages to the button, expecting the button to respond appropriately. Which it does, by sending the desired BN_CLICKED notification to the button's parent, but, it also responds appropriately to a mouse click by giving the button the focus. In almost every case that we wish to emulate a button press, we merely wish to run the handler code for the button, and do nothing more. Often, the handler code for a particular button will wish to run the handler code for another button. Instead of making identical code (which causes two places for code to be updated, if it ever changes, introducing a higher probability of bugs), the button emulates a button press to run it. But, imagine how annoying it would be if the user presses the original button, and the focus changes to the other button. This is especially annoying if the user is using the keyboard for control. A user could potentially be scrolling up and down in a list box, and suddenly, because a button press was emulated, the focus moves to the button, and now the up and down arrow keys merely change button focus from one to another, rather than scroll through the list. This is why it is best to stay away from this method.

Credit: Jason Doucette

 
Erratum 7: WM_PAINT Handler

There is a handler for WM_PAINT in just about every program in the book. However, there is something missing from every WM_PAINT handler that is explained within MSDN's documentation for WM_PAINT:

"A window may receive internal paint messages as a result of calling RedrawWindow with the RDW_INTERNALPAINT flag set. In this case, the window may not have an update region. An application should call the GetUpdateRect function to determine whether the window has an update region. If GetUpdateRect returns zero, the application should not call the BeginPaint and EndPaint functions."

Credit: Mike Sutton

 
Erratum 8: C++ Type Checking

Programming Windows, 5th Edition was written for the C programming language. However, most people reading it today likely have a C++ compiler. While a lot of the programs will compile fine in C++, some will return errors due to C++'s type checking.

For instance all calls to malloc and variants will return Compiler Error C2440: '=' : cannot convert from 'void *' to 'Data_Type_Of_Pointer *'. In C, the compiler would merely type cast the return value of malloc into the data type of the pointer (or even some other variable type) receiving its value. In C++, you must explicitly type cast the return value into the proper type. This is good, because it helps catch coding mistakes, like trying to store a pointer value into an integer. You can do a type cast as follows:

Original C code:
static PMSG pmsg ; 
pmsg = malloc (cLinesMax * sizeof (MSG)) ; 
C++ code with type cast:
static PMSG pmsg ; 
pmsg = (PMSG)malloc (cLinesMax * sizeof (MSG)) ;
However, when adding explicit typecasting for C++, you should be aware of the 64-bit Portability Issues elsewhere in this errata list. You do not want to typecast a pointer or handle to int, because it is a 32-bit integer, even on 64-bit machines. On a 64-bit machine, you'll lose 32-bits of the pointer, and the compiler will not complain since it assumes you know what you are doing if you are supplying the typecast explicitly.

Credit: every C++ coder

 
Erratum 9: Missing Index Items

The following items are missing from the index:

  • GetMenu (should exist on page 1437)
The following items are misprinted in the index:

  • GetDialogBoxUnits on page 1437 should be GetDialogBaseUnits.
Additional note from Jason Doucette:
Any errata listing misprints in the text, such as the incorrect function name is used, has resulted in the index stating that location of that incorrect name. Thus, the index is incorrect in all of these errata.

Credit: Hans Dwarshuis

 
Erratum 10: 64-bit Portability Issues

Programming Windows, 5th Edition was written for 32-bit machines. You will run into issues for all programs when compiling them on a 64-bit machine, or when attempting to detect 64-bit portability issues with a compiler switch such as Visual C++'s /Wp64 (Detect 64-Bit Portability Issues) switch.

You will note that the Win32 API has upgraded many of its functions to handle new data types that change in size depending on whether you are using a 32-bit or a 64-bit compiler. Thus, you will need to typecast parameters into these data types for them to work on both architectures. Please see Getting Ready for 64-bit Windows for more information.

Credit: Jason Doucette

Section
I

The
Basics
    Chapter 1 - Getting Started
 
Erratum 1: Typo

On page 16, Chapter 1, The MessageBox Function, on the very last line, the text mentions the HELLMSG.C program. The correct name is HELLOMSG.C.

Credit: Scott Blackledge

    Chapter 2 - An Introduction to Unicode
 
Erratum 1: Typo

On page 30, Chapter 2, Maintaining a Single Source, it states:

page 30, Chapter 2, Maintaining a Single Source
If an identifier named _UNICODE is defined and the TCHAR.H header file is included in your program, _tcslen is defined to be wcslen:

#define _tcslen wcslen

If UNICODE isn't defined, _tcslen is defined to be strlen:

#define _tcslen strlen

UNICODE is supposed to be _UNICODE.

Also, the differences between UNICODE and _UNICODE are not explained until the very last section of this chapter, even though the chapter mentions both several times. This may give the reader worries about what the exact differences between the two are, until the end of the chapter. Then, it would be required to re-read the entire chapter again for proper understanding of these terms throughout the chapter.

Basically, to maintain a single source that compiles in both ASCII and Unicode, you will need to define some identifiers. The UNICODE (without the underscore) identifier is recognized by the Win32 API. The _UNICODE (with the underscore) identifier is recognized by the C run-time library. Define it if you use any C run-time functions that have strings as parameters.

You can read more details regarding this on Raymond Chen's blog, The Old New Thing, in his TEXT vs. _TEXT vs. _T, and UNICODE vs. _UNICODE article.

Credit: Jason Doucette

 
Erratum 2: Correction

On page 27, Chapter 2, The char Data Type, the book states:

page 27, Chapter 2, The char Data Type
If you define this array as a local variable to a function, it must be defined as a static variable, as follows:

static char a[] = "Hello!" ;

However, this has not been true since the adoption of ANSI C in 1990.

Credit: John Kopplin

 
Erratum 3: Source Code Bug

On page 37, Chapter 2, A Formatting Message Box, the code listing shows a method to allow formatted text, in printf() style, to be displayed in a message box. You will notice the use of the _vsntprintf() function. This is a variant of the printf() function which allows, among other things, a restriction on the number of characters written to the output (signified by the 'n' within the function name). This is good, as it avoids the dreaded buffer overflow security problem. However, it does not guarantee the output is NULL terminated. This occurs when what you write to the buffer is larger than the buffer. It writes as many characters as it can before it runs out of room, without stopping one character sooner to append a NULL as the final character. Therefore, you must do this yourself.

The code can be fixed with one addition line of code. Place this code after the _vsntprintf() function call to 'clean up' after it:

	// ensure NULL termination
	szBuffer[sizeof (szBuffer) / sizeof (TCHAR) - 1] = NULL;

Credit: Jason Doucette

 
Erratum 4: Typo

On page 39, Chapter 2, Internationalization and This Book, the text mentions the following at the end of the first paragraph:

"If you replace _vsntprintf in SCRNSIZE.C with the Windows function wprintf (you'll also have to eliminate the second argument to the function), the Unicode version of SCRNSIZE.C will not run under Windows 98 because Windows 98 does not implement wprintfW."

However, wprintf() is the wide-character version of printf(). Charles Petzold meant to use the wsprintf() function as an example, since it is the Windows Version of the standard sprintf() function, as explained on page 36. Therefore, the text should read:

"If you replace _vsntprintf in SCRNSIZE.C with the Windows function wsprintf (you'll also have to eliminate the second argument to the function), the Unicode version of SCRNSIZE.C will not run under Windows 98 because Windows 98 does not implement wsprintfW."

Credit: Kim Gräsman

    Chapter 3 - Windows and Messages
 
Erratum 1: Typo

On page 42, Chapter 3, An Architectural Overview, it states:

Page 42, Chapter 3, An Architectural Overview
How does the application know that the user has changed the window's size? For programmers accustomed to only conventional character-mode programming, there is no mechanism for the operating system to convey information of this sort to the user.

I believe Charles Petzold meant to write:

"...to convey information of this sort to the programmer."

Credit: Jason Doucette

 
Erratum 2: Typo

On page 55, Chapter 3, Registering the Window Class, during the explaining the following statement:

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

It states:

Page 55, Chapter 3, Registering the Window Class
This handle is assigned to the bCursor field of the WNDCLASS structure.

Charles Petzold meant to write hCursor, not bCursor (that is: an H instead of a B). In the italics font of the text, they look very similar.

Credit: Jason Doucette

 
Erratum 3: Apps Hungarian Notation vs. Systems Hungarian Notation

On page 50, there is a section called 'Hungarian Notation'. It describes a variable-naming convention devised by Charles Simonyi. Charles Simonyi's original model is known as Apps Hungarian Notation. However, Charles Petzold's interpretation of his convention is incorrect. This infamous model is known as Systems Hungarian Notation. Charles Petzold gives a simple explanation of this interpretation it in his text which indicates his mistake:

"Very simply, the variable name begins with a lowercase letter or letters that denote the data type of the variable."

The mistake is "data type". In Charles Simonyi's original paper, he mentions the word 'type', and apparently, everyone took this to mean the variable's data type. Charles Simonyi clearly states in his paper, under Type Calculus:

"...the concept of "type" in this context is determined by the set of operations that can be applied to a quantity. The test for type equivalence is simple: could the same set of operations be meaningfully applied to the quantities in questions? If so, the types are thought to be the same. If there are operations that apply to a quantity in exclusion of others, the type of the quantity is different."

For example, you may have two variables of type int. One could be the x-coordinate of a pixel in a bitmap, and the other could store the length of a string. According to Charles Petzold's model, Systems Hungarian Notation, you would prefix both variables with i, because they are both of the same data type, int. However, in proper Apps Hungarian Notation, as meant by Charles Simonyi, the variable names would be prefixed by their 'type', perhaps x for the x-coordinate, and cch, meaning count of characters, for the string length. In Apps Hungarian Notation, you can easily distinguish that these two variables have no business being in the same expression together, as they are not of the same 'type'.

Joel Spolsky brought up this information a couple of times on his Joel on Software weblog. In particular, the following articles mention it:

Joel on Software: The Road to FogBugz 4.0: Part III
Joel on Software: Making Wrong Code Look Wrong

I highly recommend reading Joel's essays, as they provide a great explanation of Charles Simonyi's original idea, Apps Hungarian Notation. With examples, he explains why it works so well and why the infamous Systems Hungarian Notation, as explained by Charles Petzold, isn't as good.

As Charles Petzold is one of only seven people to win the Windows Pioneer Award, for his contribution to the success of Windows, he is largely responsible for disseminating Systems Hungarian Notation outside of Microsoft. Although, it should be noted that Charles Petzold explained this notation because it is what the Windows programmers used. It is very convenient to know the notation used by the WIndows programmers when attempting to understand Windows API functions and structures, which Charles Petzold delves into often.

Credit: Joel Spolsky

    Chapter 4 - An Exercise in Text Output
 
Erratum 1: Misleading Text

On page 99, Chapter 4, Scroll Bar Range and Position, the explanation of SetScrollRange() states:

Page 99, Chapter 4, Scroll Bar Range and Position
(If you will be calling other functions that affect the appearance of the scroll bar after you call SetScrollRange, you'll probably want to set bRedraw to FALSE to avoid excessive redrawing.)

This is somewhat misleading. Perhaps saying "If you will be immediately calling other functions..." is clearer.

Credit: Jason Doucette

 
Erratum 2: Typo

On Page 114, Chapter 4, The New SYSMETS, in the SYSMETS3.C program, the following code exists:

Page 114, Chapter 4, SYSMETS3.C
     case WM_VSCROLL:
               // Get all the vertial scroll bar information

          si.cbSize = sizeof (si) ;
          si.fMask  = SIF_ALL ;
          GetScrollInfo (hwnd, SB_VERT, &si) ;

               // Save the position for comparison later on

          iVertPos = si.nPos ;

This code is ok (with the exception of the misspelling of vertical), but this code was copied to page 115 like so:

Page 115, Chapter 4, SYSMETS3.C - Error
     case WM_HSCROLL:
               // Get all the vertial scroll bar information

          si.cbSize = sizeof (si) ;
          si.fMask  = SIF_ALL ;

               // Save the position for comparison later on

          GetScrollInfo (hwnd, SB_HORZ, &si) ;
          iHorzPos = si.nPos ;

This is not ok. The first comment should say "Get all the horizontal scroll bar information", and the GetScrollInfo() call should be before the second comment.

Please note that the above typo also occurs in both future versions of the program in SYSMETS4.C on pages 227 - 228, Chapter 6, and in SYSMETS.C on pages 321 - 322, Chapter 7.

Credit: Jason Doucette

 
Erratum 3: Typo

On page 118, Chapter 4, The New SYSMETS, the text mentions using "SB_SLOWMACHINE". This should be "SM_SLOWMACHINE".

Credit: Jason Doucette

 
Erratum 4: Scroll Bar Code Explanation

On pages 114 - 116, Chapter 4, The New SYSMETS, in the SYSMETS3.C program, the vertical scroll bar is handled differently than the horizontal scroll bar.

Firstly, the vertical scroll bar processes the scroll bar value SB_THUMBTRACK, which is sent repeatedly until the user releases the mouse button. The horizontal scroll bar processes only the SB_THUMBPOSITION scroll bar value, which is sent when the user has dragged the scroll box (thumb) and released the mouse button. So, the vertical scroll bar continually updates the window as the user moves it, and horizontal scroll bar only updates the window after the user stops moving it. Resize the window so that both scroll bars appear, and you can see for yourself. Charles Petzold must not have processed the SB_THUMBTRACK for the horizontal scroll bar to show off the differences between the two, as both messages contain the same information in their messages (i.e. The high-order word of wParam indicates the position of the scroll box), you can actually use the same processing code for both. Simply changing SB_THUMBPOSITION to SB_THUMBTRACK 'fixes' the horizontal scroll bar so that it responds immediately to scroll bar movements.

Secondly, during the update of the window, the vertical scroll bar calls ScrollWindow() and UpdateWindow(). The horizontal scroll bar only calls ScrollWindow(). The reason is that the vertical scroll bar continually updates, whereas the horizontal scroll bar does not. If UpdateWindow() was not called, on a slow machine, you could have a potential situation where the window's contents are being scrolled as the user moves the mouse, but the invalid area is not updated until the user stops moving the mouse. This is due to the fact that WM_PAINT is a low priority message. An UpdateWindow() call makes it the absolute highest priority message for just that one call, by sending a WM_PAINT message directly to the window procedure, bypassing the application's message queue. This UpdateWindow() call is not required for the horizontal scroll bar processing, as it only handles the SB_THUMBPOSITION scroll bar value, and therefore, the display is only update once - not continuously.

Please note that the above code differences also exist in both future versions of the program in SYSMETS4.C on pages 227 - 229, Chapter 6, and in SYSMETS.C on pages 321 - 323, Chapter 7.

Credit: Jason Doucette and Ariod

 
Erratum 5: Typo

On page 111, Chapter 4, How Low Can You Scroll?, the following line in the sample code in the middle of the page has a typo:

si.cbMask = SIF_RANGE | SIF_PAGE;

The SCROLLINFO structure has no cbMask field. The field's name should be fMask, as described on page 109, like so:

si.fMask = SIF_RANGE | SIF_PAGE;

Credit: Ricardo Gamez

    Chapter 5 - Basic Drawing
 
Erratum 1: Figure Typos

On page 136, Chapter 5, The Size of the Device, Figure 5-4 has two typos. tmExternalLeading should be changed to tmInternalLeading, and in the caption, FONTMETRIC should be changed to TEXTMETRIC.

Credit: Doug Beleznay

 
Erratum 2: Source Code Omission

On page 148, Chapter 5, Straight Lines, in the SINEWAVE.C program, there is a missing EndPaint() call to match the BeginPaint() call within handling the WM_PAINT message. The following line of code should be inserted immediately before the return() call that concludes the WM_PAINT message processing:

EndPaint (hwnd, &ps) ;

Credit: Ross Driedger

 
Erratum 3: Typo

On page 143, Chapter 5, Setting Pixels, the text states:

Page 143, Chapter 5, Setting Pixels - Error
Even though the Windows GPI includes SetPixel and GetPixel functions, they are not commonly used.

However, I believe that GPI should be GDI:

Page 143, Chapter 5, Setting Pixels - Correction
Even though the Windows GDI includes SetPixel and GetPixel functions, they are not commonly used.

I believe this, as opposed to API (Application Programming Interface) suggested by John Kopplin, as the previous two paragraphs were just speaking about the Windows GDI (Graphics Device Interface) and the SetPixel() and GetPixel() functions.

Credit: Jason Doucette, based on John Kopplin's report

 
Erratum 4: Correction

On pages 167 - 168, Chapter 5, Drawing Modes, starting at the bottom of page 167 continuing into page 168, the text states:

Pages 167 - 168, Chapter 5, Drawing Modes - Error
The R2_NOT drawing mode always inverts the destination color to determine the color of the line, regardless of the color of the pen. For example, a line drawn on a cyan destination will appear as magenta.

Cyan is 00FFFF (in RRGGBB). The inverse of this is FF0000, which is red. Magenta is purple - a combination of blue and red, FF00FF. Therefore, the text should state:

Pages 167 - 168, Chapter 5, Drawing Modes - Correction
The R2_NOT drawing mode always inverts the destination color to determine the color of the line, regardless of the color of the pen. For example, a line drawn on a cyan destination will appear as red.

Credit: Peter Ehrhart

 
Erratum 5: Miscalculation

On page 137, Chapter 5, The Size of the Device, in the third last paragraph, Petzold mentions that a 17-inch monitor would have an actual display size of about 12 inches by 9 inches. However, those are the values for a viewable diagonal of 15 inches. The viewable size of 17-inch monitor is normally close to 16 inches. This would make the actual display size about 12.8 inches by 9.6 inches, which would round to 13 inches by 10 inches. The figures he creates are used only for a quick approximation. Therefore, I am not sure if he created these figures quickly by guessing that the viewable size of a 17-inch monitor is 15 inches, or that he was thinking of a 15-inch monitor when he did the calculations, and assumed that the viewable size was 15 inches, as well. The fact that he uses the words "the actual display size" indicates that he is well aware of the fact that the viewable size is smaller than the monitor size. In either case, he is slightly wrong, but the numbers are close enough to the real values for him to get his point across.

Credit: Dani Berg

 
Erratum 6: Reference Mistake

On page 190, Chapter 5, The MM_ISOTROPIC Mapping Mode, the last paragraph states:

"GetDeviceCaps with the HORZRES and VERTRES indexes return the dimensions of the device in millimeters."

The paragraph should state:

"GetDeviceCaps with the HORZSIZE and VERTSIZE indexes return the dimensions of the device in millimeters."

On the MSDN GetDeviceCaps function page, it states that HORZSIZE and VERTSIZE return the width and height, in millimeters, of the physical screen. It also states that HORZRES and VERTRES return the width and height, in pixels, of the screen. You can see that Charles Petzold used these parameters correctly in the code preceding the paragraph, he merely referred to the wrong parameters during his explanation.

Credit: Khoguan Pan

 
Erratum 7: Insignificant Misprint

On page 167, Chapter 5, Drawing Modes, in the first column under the horizontal line in the table about all sixteen ROP2 drawing modes, the word 'Results' should be 'Result'. Yes, it's minor, but this keeps in synch with the tables on pages 658 and 660 in Chapter 14.

Credit: Hans Dwarshuis

 
Erratum 8: Missing Parameter

On page 183, Chapter 5, Working with MM_TEXT, the second section of code shows how to use the SetWindowOrgEx() function:
     SetWindowOrgEx (hdc, 0, cyChar * iVscrollPos) ;
But, it is missing the final (fourth) paramter:
     SetWindowOrgEx (hdc, 0, cyChar * iVscrollPos, NULL) ;
This last paramter is a pointer to a POINT structure that receives the previous origin of the window. If it is NULL, as in all of Petzold's cases, the parameter is not used.

Credit: DollfaceYY

 
Erratum 9: Mistype

On page 187, Chapter 5, The Metric Mapping Modes, the code at the very top of the page references a DptoLP() function. The second letter, 'p', in the function name should be capitalized: DPtoLP().

Credit: DollfaceYY

 
Erratum 10: Missing Parameter

On page 189, Chapter 5, The MM_ISOTRPOIC Mapping Mode, the code at the very top of the page references the SetMapMode() function. It takes two parameters, but only one is passed. It should be called like so:
SetMapMode (hdc, MM_ISOTROPIC) ;
You can see correct usage of this function on the previous page, page 188.

Credit: DollfaceYY

    Chapter 6 - The Keyboard
 
Erratum 1: Typo

On Page 227, Chapter 6, in the SYSMETS4.C program, the following code exists:

Page 227, Chapter 6, SYSMETS4.C
     case WM_VSCROLL:
               // Get all the vertical scroll bar information

          si.cbSize = sizeof (si) ;
          si.fMask  = SIF_ALL ;
          GetScrollInfo (hwnd, SB_VERT, &si) ;

               // Save the position for comparison later on

          iVertPos = si.nPos ;

This code is ok, but this code was copied to page 228 like so:

Page 228, Chapter 6, SYSMETS4.C - Error
     case WM_HSCROLL:
               // Get all the vertical scroll bar information

          si.cbSize = sizeof (si) ;
          si.fMask  = SIF_ALL ;

               // Save the position for comparison later on

          GetScrollInfo (hwnd, SB_HORZ, &si) ;
          iHorzPos = si.nPos ;

This is not ok. The first comment should say "Get all the horizontal scroll bar information", and the GetScrollInfo() call should be before the second comment.

Please note that the above typo also occurs in the prior version of the program in SYSMETS3.C on pages 114 - 115, Chapter 4, and in the final version of the program in SYSMETS.C on pages 321 - 322, Chapter 7.

Credit: Jason Doucette

 
Erratum 2: Typo

On page 264, Chapter 6, Caret Functions, in the second to last paragraph, the sentence should be changed from DestroyWindow to DestroyCaret.

Credit: John Dlugosz and Doug Yip

 
Erratum 3: Scroll Bar Code Explanation

On pages 227 - 229, Chapter 6, Enhancing SYSMETS for the Keyboard, in the SYSMETS4.C program, the vertical scroll bar is handled differently than the horizontal scroll bar.

Firstly, the vertical scroll bar processes the scroll bar value SB_THUMBTRACK, which is sent repeatedly until the user releases the mouse button. The horizontal scroll bar processes only the SB_THUMBPOSITION scroll bar value, which is sent when the user has dragged the scroll box (thumb) and released the mouse button. So, the vertical scroll bar continually updates the window as the user moves it, and horizontal scroll bar only updates the window after the user stops moving it. Resize the window so that both scroll bars appear, and you can see for yourself. Charles Petzold must not have processed the SB_THUMBTRACK for the horizontal scroll bar to show off the differences between the two, as both messages contain the same information in their messages (i.e. The high-order word of wParam indicates the position of the scroll box), you can actually use the same processing code for both. Simply changing SB_THUMBPOSITION to SB_THUMBTRACK 'fixes' the horizontal scroll bar so that it responds immediately to scroll bar movements.

Secondly, during the update of the window, the vertical scroll bar calls ScrollWindow() and UpdateWindow(). The horizontal scroll bar only calls ScrollWindow(). The reason is that the vertical scroll bar continually updates, whereas the horizontal scroll bar does not. If UpdateWindow() was not called, on a slow machine, you could have a potential situation where the window's contents are being scrolled as the user moves the mouse, but the invalid area is not updated until the user stops moving the mouse. This is due to the fact that WM_PAINT is a low priority message. An UpdateWindow() call makes it the absolute highest priority message for just that one call, by sending a WM_PAINT message directly to the window procedure, bypassing the application's message queue. This UpdateWindow() call is not required for the horizontal scroll bar processing, as it only handles the SB_THUMBPOSITION scroll bar value, and therefore, the display is only update once - not continuously.

Please note that the above code differences also exist in the prior version of the program in SYSMETS3.C on pages 114 - 116, Chapter 4, and in the final version of the program in SYSMETS.C on pages 321 - 323, Chapter 7.

Credit: Jason Doucette and Ariod

 
Erratum 4: Typo

On page 243, Chapter 6, The Foreign-Language Keyboard Problem, in the middle of the second paragraph, a starting parenthesis appears with no closing parenthesis at the end of the paragraph.

Please note: This erratum is obviously of no real concern to the reader. It is only included for completeness, in case Charles Petzold reviews this page for corrections to the next version of Programming Windows.

Credit: Kim Gräsman

 
Erratum 5: Typo

On pages 244, Chapter 6, Character Sets and Fonts, in the third last paragraph on the page, the text speaks about bitmap fonts. In one occasion, the text refers to "Bitmaps fonts", which is just a typo for "Bitmap fonts".

Credit: Kim Gräsman

 
Erratum 6: Memory Deallocation

On pages 271, Chapter 6, in the TYPER.C program, the text buffer is not deallocated when the application terminates. Add the following code to the WM_DESTORY message handler to free the memory of the buffer before the application ends:
          if (pBuffer != NULL)
               free (pBuffer) ;

Credit: Olivier Langlois

    Chapter 7 - The Mouse
 
Erratum 1: Typo

On page 303, Chapter 7, Child Windows in CHECKER, the top of the page mentions using the SetWindowWord() function, when the program actually uses the SetWindowLong() function. The SetWindowWord() function is obsolete.

Credit: Jason Doucette

 
Erratum 2: Typo

On Page 321, Chapter 7, THE MOUSE WHEEL, in the SYSMETS.C program, the following code exists:

Page 321, Chapter 7, SYSMETS.C
     case WM_VSCROLL:
               // Get all the vertical scroll bar information

          si.cbSize = sizeof (si) ;
          si.fMask  = SIF_ALL ;
          GetScrollInfo (hwnd, SB_VERT, &si) ;

               // Save the position for comparison later on

          iVertPos = si.nPos ;

This code is ok, but this code was copied to page 322 like so:

Page 322, Chapter 7, SYSMETS.C - Error
     case WM_HSCROLL:
               // Get all the vertical scroll bar information

          si.cbSize = sizeof (si) ;
          si.fMask  = SIF_ALL ;

               // Save the position for comparison later on

          GetScrollInfo (hwnd, SB_HORZ, &si) ;
          iHorzPos = si.nPos ;

This is not ok. The first comment should say "Get all the horizontal scroll bar information", and the GetScrollInfo() call should be before the second comment.

Please note that the above typo also occurs in both prior version of the program in SYSMETS3.C on pages 114 - 115, Chapter 4, and in SYSMETS4.C on pages 227 - 228, Chapter 6.

Credit: Jason Doucette

 
Erratum 3: Source Code Error

On page 316, Chapter 7, The BLOKOUT2 Program, in the BLOKOUT2.C program, there is a signed/unsigned mismatch in the WM_MOUSEMOVE and WM_LBUTTONUP message processing:

Page 316, Chapter 7, BLOKOUT2.C - Error
     case WM_MOUSEMOVE :
          if (fBlocking)
          {
               SetCursor (LoadCursor (NULL, IDC_CROSS)) ;
               
               DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
               
               ptEnd.x = LOWORD (lParam) ;
               ptEnd.y = HIWORD (lParam) ;
               
               DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
          }
          return 0 ;
          
     case WM_LBUTTONUP :
          if (fBlocking)
          {
               DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
               
               ptBoxBeg   = ptBeg ;
               ptBoxEnd.x = LOWORD (lParam) ;
               ptBoxEnd.y = HIWORD (lParam) ;

The results of the LOWORD and HIWORD macros should be cast to short values before being assigned to the fields of the POINT structures:

Page 316, Chapter 7, BLOKOUT2.C - Correction
     case WM_MOUSEMOVE :
          if (fBlocking)
          {
               SetCursor (LoadCursor (NULL, IDC_CROSS)) ;
               
               DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
               
               ptEnd.x = (short) LOWORD (lParam) ;
               ptEnd.y = (short) HIWORD (lParam) ;
               
               DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
          }
          return 0 ;
          
     case WM_LBUTTONUP :
          if (fBlocking)
          {
               DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
               
               ptBoxBeg   = ptBeg ;
               ptBoxEnd.x = (short) LOWORD (lParam) ;
               ptBoxEnd.y = (short) HIWORD (lParam) ;

Credit: Paul Middleton

 
Erratum 4: Source Code Omission

On Page 318, Chapter 7, THE MOUSE WHEEL, in the SYSMETS.C program, there are two macros omitted in the source code in the book. This erratum is only partially stated within the readme.txt file that accompanies the text book:

\readme.txt
Some programs use features that are new with Windows 98 and Windows
NT 5. At the time of the creation of this CD, the Windows header
files included with Visual C++ 6 and distributed via MSDN and the
Microsoft web site did not assume Windows 98 development as a
default. Thus, to use Windows 98 features, a #define statement must
appear before the #include statement for the Windows header files,
like this:

    #define WINVER 0x0500
    #include 

This #define statement is included in the appropriate programs on
the CD but is not shown in the program listings in the book.

I will fully explain this particular situation:

Page 318, Chapter 7, SYSMETS.C - Incorrect
/*---------------------------------------------------
   SYSMETS.C -- Final System Metrics Display Program 
                (c) Charles Petzold, 1998

  ---------------------------------------------------*/

#include <windows.h>
#include "sysmets.h"

The two definitions are:

#define WINVER 0x0500
#define _WIN32_WINNT 0x0500   // for Mouse Wheel support
The source code on the companion CD has the two macros defined:

\Chap07\SysMets\SYSMETS.C - Correct
/*---------------------------------------------------
   SYSMETS.C -- Final System Metrics Display Program 
                (c) Charles Petzold, 1998
  ---------------------------------------------------*/

#define WINVER 0x0500
#define _WIN32_WINNT 0x0500   // for Mouse Wheel support
#include <windows.h>
#include "sysmets.h"

If you omit the following macro:

#define WINVER 0x0500

The program will not have access to the following required definitions:

SM_XVIRTUALSCREEN
SM_YVIRTUALSCREEN
SM_CXVIRTUALSCREEN
SM_CYVIRTUALSCREEN
SM_CMONITORS
SM_SAMEDISPLAYFORMAT
If you omit the following macro:

#define _WIN32_WINNT 0x0500

The program will not have access to the following required definitions:

SPI_GETWHEELSCROLLLINES
WHEEL_DELTA
WM_MOUSEWHEEL
Also, the text book lacks any explanation of why these definitions are required or what exactly WINVER and _WIN32_WINNT mean. Basically, certain features, such as the mouse wheel, depend on a particular version of Windows. These definitions are declared using conditional code, such as the following, in WINUSER.H:

#if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400)
#define WM_MOUSEWHEEL                   0x020A
#define WM_MOUSELAST                    0x020A
#else
#define WM_MOUSELAST                    0x0209
#endif /* if (_WIN32_WINNT < 0x0400) */
When you define certain macros, you set a target version of Windows for your application. The compiler will then detect whether your application uses functions that are not supported on its target, by returning error C2065: undeclared identifier for all improper references. The following URL shows a table that indicates which macros you must define to target a particular operating system, and will be of much value for you: MSDN - Using the Windows Headers. Please note the warning after the table which indicates that sometimes you have to target the next major operating system release, if the feature you require was added to a service pack for the version of Windows you wish to target. For example, the WINDOWINFO Structure requires Windows 98, which this table states that WINVER must be set to at least 0x0410. You will find that it does not work unless you target the next major operating system release, Windows Me, which requires WINVER to be set to at least 0x0500.

In our program, we wished to set the target machine as Windows 2000, therefore we need to explicitly define _WIN32_WINNT as 0x0500 or greater. We defined it as 0x0500 with the following statement:

#define _WIN32_WINNT 0x0500

You can define the symbols with a statement like the above, or you can specify the following compiler option:

/D_WIN32_WINNT=0x0500

In MSVC++ 6.0, you can specify compiler options by going to the Projects menu, Settings, C/C++ tab.

Credit: Charles Petzold and Jason Doucette

 
Erratum 5: Scroll Bar Code Explanation

On pages 321 - 323, Chapter 7, The MOUSE WHEEL, in the SYSMETS.C program, the vertical scroll bar is handled differently than the horizontal scroll bar.

Firstly, the vertical scroll bar processes the scroll bar value SB_THUMBTRACK, which is sent repeatedly until the user releases the mouse button. The horizontal scroll bar processes only the SB_THUMBPOSITION scroll bar value, which is sent when the user has dragged the scroll box (thumb) and released the mouse button. So, the vertical scroll bar continually updates the window as the user moves it, and horizontal scroll bar only updates the window after the user stops moving it. Resize the window so that both scroll bars appear, and you can see for yourself. Charles Petzold must not have processed the SB_THUMBTRACK for the horizontal scroll bar to show off the differences between the two, as both messages contain the same information in their messages (i.e. The high-order word of wParam indicates the position of the scroll box), you can actually use the same processing code for both. Simply changing SB_THUMBPOSITION to SB_THUMBTRACK 'fixes' the horizontal scroll bar so that it responds immediately to scroll bar movements.

Secondly, during the update of the window, the vertical scroll bar calls ScrollWindow() and UpdateWindow(). The horizontal scroll bar only calls ScrollWindow(). The reason is that the vertical scroll bar continually updates, whereas the horizontal scroll bar does not. If UpdateWindow() was not called, on a slow machine, you could have a potential situation where the window's contents are being scrolled as the user moves the mouse, but the invalid area is not updated until the user stops moving the mouse. This is due to the fact that WM_PAINT is a low priority message. An UpdateWindow() call makes it the absolute highest priority message for just that one call, by sending a WM_PAINT message directly to the window procedure, bypassing the application's message queue. This UpdateWindow() call is not required for the horizontal scroll bar processing, as it only handles the SB_THUMBPOSITION scroll bar value, and therefore, the display is only update once - not continuously.

Please note that the above code differences also exist in both prior version of the program in SYSMETS3.C on pages 114 - 116, Chapter 4, and in SYSMETS4.C on pages 227 - 229, Chapter 6.

Credit: Jason Doucette and Ariod

 
Erratum 6: Typo

On page 276, Chapter 7, Client-Area Mouse Messages, on the second to last line of the page, there is a typo.

The following code is incorrect:

wparam & MK_SHIFT

The 'p' should be capitalized, like so:

wParam & MK_SHIFT

Credit: Brandino Andreas

 
Erratum 7: Precedence Clarification

On page 312, Chapter 7, Blocking Out a Rectangle, in the BLOKOUT1.C program, the switch case for WM_CHAR attempts to see if the user is in the process of making a rectangle selection (if fBlocking is true) and if the used pressed the ESC key (if wParam is equal to 0x1B):

Page 312, Chapter 7, BLOKOUT1.C - Improper
case WM_CHAR :
	if (fBlocking & wParam == '\x1B')     // i.e., Escape

Note the use of two operators: the equality operator (==) and the bitwise-AND operator (&). The code does not clarify which operator should be evaluated first. Without intimate knowledge of the order of precedence of such operators, this could be a bug (even though, after inspection, it is not a bug). Due to the likely chance of a bug in such code, modern compilers will return the following warning:

warning C4554: '&' : check operator precedence for possible error; use parentheses to clarify precedence

To make this code absolutely clear, and to avoid warning messages, add parenthesis to clarify the precedence:

Page 312, Chapter 7, BLOKOUT1.C - Proper
case WM_CHAR :
	if (fBlocking & (wParam == '\x1B'))     // i.e., Escape

I should note that the code is improper only in the book. The code on the CD that accompanies the book has the parenthesis added.

Credit: Mike Malone

    Chapter 8 - The Timer
 
Erratum 1: WinXP Update

On page 333, Chapter 8, Method One, the text states:

Page 333, Chapter 8, Method One
Here's a revealing experiment: First invoke the Display applet from the Control Panel, and select the Effects tab. Make sure the "Show window contents while dragging" button is unchecked. Now try moving or resizing the BEEPER1 window. This causes the program to enter a "modal message loop." Windows prevents anything from interfering with the move or resize operation by trapping all messages through a message loop inside Windows rather than the message loop in your program. Most messages to a program's window that come through this loop are simply discarded, which is why BEEPER1 stops beeping.

I know that the book was not written for Windows XP, but I thought I would point out that Windows XP no longer traps messages when moving / resizing a window with the "Show window contents while dragging" button unchecked.

Credit: Jason Doucette

 
Erratum 2: Typo

On page 351, Chapter 8, Building an Analog Clock, there is a typo. A paragraph in the middle of the page states:

Page 351, Chapter 8, Building an Analog Clock
The DrawHands function draws the hour, minute, and second hands of the clock. The coordinates defining the outlines of the hands (as they appear when pointing straight up) are stored in an array of POINT structures. Depending upon the time, these coordinates are rotated using the RotatePoint function and are displayed with the Windows Polyline function. Notice that the hour and minute hands are displayed only if the bChange parameter to DrawHands is TRUE. When the program updates the clock hands, in most cases the hour and minute hands will not need to be redrawn.

However, there is no bChange variable in DrawHands. The variable is fChange.

Credit: Jason Doucette

 
Erratum 3: Test Code Remnants

On page 354, Chapter 8, USING THE TIMER FOR A STATUS REPORT, in the WHATCLR.C program, there is a line of code in the WM_TIMER handler that is unnecessary. It is the following line:

SetPixel (hdcScreen, pt.x, pt.y, 0) ;

This line of code plots a black pixel to be displayed after reading the pixel's color. I would assume that this code was only used during the debugging or testing phases of the program, and is unnecessary. In fact, it is unwanted, as it writes to the display or on top of other windows which is does not own.

Merely remove this line for the program to operate properly. This line only exists in the text book source code; it does not exist in the program samples that accompany the text book.

Credit: Strayfire

    Chapter 9 - Child Window Controls
 
Erratum 1: Typo

On page 357, Chapter 9, Child Window Controls, the first paragraph states:

Page 357, Chapter 9, Child Window Controls
Although the CHECKER1 and CHECKER2 versions of this program use only one main window, the CHECKER3 version uses a child window for each rectangle. The rectangles are maintained by a separate window procedure named ChildProc.

However, the window procedure for the CHECKER3 program is called ChildWndProc.

Credit: Jason Doucette

 
Erratum 2: Typo

On Page 363, Chapter 9, Creating the Child Windows, the parameter list on the bottom of the page for CreateWindow has a few typos:

Page 363, Chapter 9, Creating the Child Windows
Class name
TEXT ("button")
Window text
button[i].szText
Window style
WS_CHILD ¦ WS_VISIBLE ¦ button[i].iStyle
x position
cxChar
y position
cyChar * (1 + 2 * i)
Width
20 * xChar
Height
7 * yChar / 4
Parent window
hwnd
Child window ID
(HMENU) i
Instance handle
((LPCREATESTRUCT) lParam) -> hInstance
Extra parameters
NULL

For the Width and Height parameter, it shows xChar and yChar. They should be cxChar and cyChar as used in the x position and y position parameters.

Credit: Jason Doucette

 
Erratum 3: Text Correction

On page 391, Chapter 9, The COLORS1 Program, the second paragraph states:

Page 391, Chapter 9, The COLORS1 Program
COLORS1 creates its normal overlapped window and the 10 child windows within the WinMain function using CreateWindow.

This is not true. The main normal overlapped window is created from WinMain(), as normal. The 10 child windows are created in the WndProc() function from within the WM_CREATE message. It is true that all are created with the CreateWindow() function, however.

Credit: Jason Doucette

 
Erratum 4: Text Correction

On page 391, Chapter 9, The COLORS1 Program, the last paragraph states:

Page 391, Chapter 9, The COLORS1 Program
When the WndProc window procedure receives a WM_VSCROLL message, the high word of the lParam parameter is the handle to the child window.

As far as I know, handles are 32-bit integers, therefore the high word (a 16-bit number) of anything cannot be a handle. Also, the MSVC++ 6.0 MSDN documentation on WM_VSCROLL states:

"hwndScrollBar = (HWND) lParam; // handle to scroll bar"

This is consistent with your code for this example (COLORS1.C), which uses the entire 32-bit lParam value as the first parameter to GetWindowLong(), in this statement on page 388:

"i = GetWindowLong ((HWND) lParam, GWL_ID) ;"

Credit: Jason Doucette

 
Erratum 5: Typo

On page 393, Chapter 9, Coloring the Background, the first paragraph states at the end:

Page 393, Chapter 9, Coloring the Background
Just as we were able to get and set the scroll bar window procedure using GetWindowLong and SetWindowLong, we can get and set the handle to this brush using GetClassWord and SetClassWord.

GetClassWord() and SetClassWord() are obsolete. The text should state GetClassLong() and SetClassLong(), as in the example that follows this text, as well as the code for COLORS1.C.

Credit: Jason Doucette

 
Erratum 6: Text Correction (Possibly only for WinXP)

On page 398, Chapter 9, The Edit Class Styles, the text states:

Page 398, Chapter 9, The Edit Class Styles
For a multiline edit control, text wordwraps unless you use the ES_AUTOHSCROLL style, in which case you must press the ENTER key to start a new line.

This implies that wordwrapping is turned 'off' by including the ES_AUTOHSCROLL style, I am not sure if this is the case for Win98 or WinNT, but for my Windows XP system, wordwrapping is not dependant solely on the ES_AUTOHSCROLL style. It is also turned 'off' by including the WS_HSCROLL window style.

Credit: Jason Doucette

 
Erratum 7: Text Correction

On page 399, Chapter 9, Edit Control Notification, the text explains the notification codes for Edit Controls, as shown:

Page 399, Chapter 9, Edit Control Notification

The notification codes are shown below:

EN_SETFOCUS Edit control has gained the input focus.
EN_KILLFOCUS Edit control has lost the input focus.
EN_CHANGE Edit control's contents will change.
EN_UPDATE Edit control's contents have changed.
EN_ERRSPACE Edit control has run out of space.
EN_MAXTEXT Edit control has run out of space on insertion.
EN_HSCROLL Edit control's horizontal scroll bar has been clicked.
EN_VSCROLL Edit control's vertical scroll bar has been clicked.


The problem is when EN_CHANGE and EN_UPDATE. Unlike the implications made by the table above, for both of these messages, the control's contents have already changed. The difference is when the message is sent. As explained in the MSDN:

Excerpts from MSDN pages for EN_CHANGE and EN_UPDATE
EN_CHANGE Unlike the EN_UPDATE notification message, this notification message is sent after the system updates the screen.
EN_UPDATE The EN_UPDATE notification message is sent when an edit control is about to display altered text. This notification message is sent after the control has formatted the text, but before it displays the text.

Even if a person were to take the meaning of the text's words "control's contents" as the actual displaying of characters (which is, perhaps, what Charles Petzold meant), the messages are explained backwards. EN_UPDATE is sent before the display is changed (allowing the program to modify the size of the control before it happens), and EN_CHANGE is sent after the display is changed.

Credit: Jason Doucette

 
Erratum 8: Source Code Typo

On page 413, Chapter 9, A head for Windows, the HEAD.C file uses window subclassing. The following is the statement that sets the new windows procedure for the listbox, and saves the old windows procedure that the list box used to call:

          OldList = (WNDPROC) SetWindowLong (hwndList, GWL_WNDPROC,
                                               (LPARAM) ListProc) ;
Note that the third parameter to SetWindowLong() is supposed to be a LONG value, but in the program it is type cast into an LPARAM value. The code compiles, since LPARAM is compatible with LONG, but it is not the proper type.

Credit: Jason Doucette

 
Erratum 9: Text Correction

On page 391, Chapter 9, The COLORS1 Program, the sentence immediately before the line of code states:

Page 391, Chapter 9, The COLORS1 Program - Error
We can use GetWindowWord to get the window ID number:

i = GetWindowLong ((HWND) lParam, GWL_ID) ;

GetWindowWord() is an obsolete function, and the code sample does not use it. It uses the proper function, GetWindowLong(). The correct text is as follows:

Page 391, Chapter 9, The COLORS1 Program - Correction
We can use GetWindowLong to get the window ID number:

i = GetWindowLong ((HWND) lParam, GWL_ID) ;

Credit: John Kopplin

 
Erratum 10: Additional Information

On page 357, Chapter 9, Child Window Controls, in the last paragraph, the text states:

Page 357, Chapter 9, Child Window Controls
"What would message be set to? Well, anything you want, really, as long as the numeric value is set to WM_USER or above. These numbers represent a range of messages that do not conflict with the predefined WM_ messages."

However, WM_APP is a safer alternative. This is what MSDN states about the differences between WM_USER and WM_APP:

MSDN page on WM_USER and WM_APP:
"Message numbers in the second range (WM_USER through 0x7FFF) can be defined and used by an application to send messages within a private window class. These values cannot be used to define messages that are meaningful throughout an application, because some predefined window classes already define values in this range. For example, predefined control classes such as BUTTON, EDIT, LISTBOX, and COMBOBOX may use these values. Messages in this range should not be sent to other applications unless the applications have been designed to exchange messages and to attach the same meaning to the message numbers.

Message numbers in the third range (WM_APP through 0xBFFF) are available for application to use as private messages. Message in this range do not conflict with system messages.
"

For example, the IsDialogMessage() Function can send the DM_GETDEFID and DM_SETDEFID messages to the window, which are defined as WM_USER and WM_USER+1 in the winuser.h header file. Therefore, I would suggest using WM_APP+n instead of WM_USER+n in the creation of your own message values.

I also suggest reading an article by Joseph M. Newcomer on Message Management, which explains his thoughts about managing user defined messages, and an article on Raymond Chen's blog, The Old New Thing, Which message numbers belong to whom?, which explains in detail the differences between all four types of Windows message numbers.

Credit: Jason Doucette

 
Erratum 11: Better Code

On page 368, Chapter 9, Check Boxes, the book explains what you must pass in the wParam parameter in a SendMessage() call when sending a BM_SETCHECK Message to a check box control to set or remove the check mark:

"The wParam parameter is set to 1 to create a check mark and to 0 to remove it."

While this statement is correct, the BM_SETCHECK Message has identifiers already created that hold these values, so you need not remember them. They are:
  • BST_CHECKED is defined as 1, which sets the button state to checked.
  • BST_UNCHECKED is defined as 0, which sets the button state to cleared.
  • BST_INDETERMINATE is defined as 2, which sets the button state to grayed, indicating an indeterminate state. (This is only used for buttons with the BS_3STATE or BS_AUTO3STATE style).
Charles Petzold exclusively uses the numeric values, rather than the identifiers, throughout his book (with one exception: the PICKFONT.C program in Chapter 17, on page 1018). However, it would be better to use the identifiers so that your code is more readable, and that your code continues to work in the extremely unlikely chance that these values should ever change. Notice that this will make the nifty first SendMessage() example on page 368 a little more difficult to pull off. An alternative is to use the conditional operator as in the following example:

SendMessage ((HWND) lParam, BM_SETCHECK, (WPARAM)
    (SendMessage ((HWND) lParam, BM_GETCHECK, 0, 0) == BST_CHECKED
        ? BST_UNCHECKED // uncheck it, if checked
        : BST_CHECKED), // check it, if unchecked
    0) ;

Credit: Jason Doucette

 
Erratum 12: Source Code Bug

On page 415, Chapter 9, HEAD.C, the ListProc() function exists as our own window procedure for the list box control. This is known as windows subclassing, first explained on page 393, Chapter 9, Windows Subclassing. This windows procedure is run first, to handle additional functionality desired (in this case, we wish for the ENTER key press to signal a double click on the current selection). Our windows procedure call the original windows procedure to handle all of the pre-programmed functionality of the window.

A bug exists within this windows procedure. The following if statement contains the bug:

Page 415, Chapter 9, HEAD.C - Error
     if (message == WM_KEYDOWN && wParam == VK_RETURN)
          SendMessage (GetParent (hwnd), WM_COMMAND, 
                       MAKELONG (1, LBN_DBLCLK), (LPARAM) hwnd) ;

The problem is in the MAKELONG Macro. It combines two WORDs to create a LONG. The first parameter is the low-order WORD, the second is the high-order WORD. The LONG we are creating is the WPARAM parameter for the WM_COMMAND Notification we are sending to the list box window to simulate a double click. WM_COMMAND expects its WPARAM parameter to have the notification code stored in the high-order WORD, and the identifier of the control in the low-order WORD. The high-order WORD is correct, as it equals LBN_DBLCLK, which is the proper notification code for a double click. The low-order WORD is incorrect, although it contains the proper numeric constant. It should be the identifier of the control, which is ID_LIST, defined at the top of the program on page 411, which is passed to the CreateWindow() function on page 413 to create the list box. If this definition were to ever change, then the window subclassing would not work. Change ID_LIST to anything other than 1, and you will see our subclassed windows procedure fail. Although the program, as is, still works, it is still a bug. Why? Because the code does not maintain the usage of this value automatically throughout itself, which is the purpose of the definition to begin with. The definition also helps ensure the code is readable, to yourself and others.

Fix the bug by changing the 1 to ID_LIST, as follows:

Page 415, Chapter 9, HEAD.C - Correction
     if (message == WM_KEYDOWN && wParam == VK_RETURN)
          SendMessage (GetParent (hwnd), WM_COMMAND, 
                       MAKELONG (ID_LIST, LBN_DBLCLK), (LPARAM) hwnd) ;

Now you can change ID_LIST to any value you want, except, of course, the value of the other child windows in its parent. In this case, set it to anything other than what ID_TEXT is set to, and the windows subclassing will still work as expected, unlike before.

Credit: Jason Doucette

 
Erratum 13: Improper Subclass Implementation

On page 385, Chapter 9, in the COLORS1.C program, and on page 411, Chapter 9, in the HEAD.C program, the programs use windows subclassing. However, the code does not remove the subclass when the window terminates. As explain in Raymond Chen's blog: "One gotcha that isn't explained clearly in the documentation is that you must remove your window subclass before the window being subclassed is destroyed."

Because the code in these programs have nothing to clean up, they are technically ok. However, if the subclass allocated memory or did something else which had to be cleaned up before termination, then they would be improper. It is best to explicitly clean up all subclassed windows to ensure proper code. Note the available functions at your disposal mentioned in Raymond Chen's article, which are explained on the MSDN article, Subclassing Controls.

The problem also exists on page 1336, Chapter 22, in the WAKEUP.C program.

Credit: Jason Doucette

 
Erratum 14: Logic Error

On page 407, Chapter 9, A Simple List Box Application, in the ENVIRON.C program, the FillListBox function has a logic error. The function uses the GetEnvironmentStrings function to receive a pointer to the environment block, and the FreeEnvironmentStrings function to free this block. As you can see within the code, the function stores the results of GetEnvironmentStrings into pVarBlock, and then later passes this variable to FreeEnvironmentStrings. The problem is, is that pVarBlock is modified in the meantime. If you were to check the return value for FreeEnvironmentStrings, you will see it is 0, indicating an error. Calling GetLastError will show the error code 87 (ERROR_INVALID_PARAMETER).

To fix this, we must merely store the original return value of GetEnvironmentStrings in a variable that will not be modified, and pass this unchanged value to FreeEnvironmentStrings. This can be accomplished as follows:

Change this line of code:
pVarBlock = GetEnvironmentStrings () ; // Get pointer to environment block 
Into this:
TCHAR* pEBlock;
pEBlock = GetEnvironmentStrings () ;	// Get pointer to environment block
pVarBlock = pEBlock;

And change this line of code:
FreeEnvironmentStrings (pVarBlock) ; 
Into this:
FreeEnvironmentStrings (pEBlock) ;

Credit: John Callas

    Chapter 10 - Menus and Other Resources
 
Erratum 1: Text Correction

On page 471, Chapter 10, Loading the Accelerator Table, the last paragraph states:

Page 471, Chapter 10, Loading the Accelerator Table
As with icons, cursors, and menus, you can use a number for the accelerator table name and then use that number in the LoadAccelerators statement with the MAKEINTRESOURCE macro or enclosed in quotation marks and preceded by a # character.

The last part should read: "...or enclosed in the TEXT macro with quotation marks and preceded by a # character." as it was explained on page 426, Chapter 10, Getting a Handle on Icons:

Page 426, Chapter 10, Getting a Handle on Icons
You can reference the icon using one of two methods. The obvious one is this:

hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (125)) ;

The obscure method is this:

hIcon = LoadIcon (hInstance, TEXT ("#125")) ;

Windows recognizes the initial # character as prefacing a number in ASCII form.

Credit: Jason Doucette

 
Erratum 2: Source Code Error

On page 476, Chapter 10, the POPPAD2.C has an error in its code. The WM_COMMAND section of the code reads as follows:

page 476, Chapter 10, POPPAD2.C
case WM_COMMAND:
     if (lParam)
     {
          if (LOWORD (lParam) == ID_EDIT &&
                    (HIWORD (wParam) == EN_ERRSPACE ||
                     HIWORD (wParam) == EN_MAXTEXT))
               MessageBox (hwnd, TEXT ("Edit control out of space."),
                           szAppName, MB_OK | MB_ICONSTOP) ;
          return 0 ;
     }
     else ...

The following line:

if (LOWORD (lParam) == ID_EDIT &&

should be:

if (LOWORD (wParam) == ID_EDIT &&

lParam is a HWND, which is the handle of the control (which can be NULL, if it is not a control), which we have already tested to make sure we are processing a control. wParam's HIWORD is the notification code, and its LOWORD is the control id, which is what should be test