Jason Doucette / Xona Games
location: Yarmouth, Nova Scotia, Canada
contact: or other methods
social networks: facebook myspace hi5
HOME | RÉSUMÉ | PROJECTS/GAMES | GFX | A.I. | TRANSCRIPTS | WORLD RECORDS | WALLPAPERS | CONTACT
PROGRAMMING WINDOWS 5th ED ERRATA ADDENDUM | DOMAIN HACKS SUGGEST | MATTHEW DOUCETTE | XONA GAMES



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

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 tested against ID_EDIT.

Without this fix, the code compiles and runs fine, with the exception that the message box for an 'out of space' error is not displayed if the edit control is filled.

Credit: Jason Doucette

 
Erratum 3: Misleading Text

On page 441, Chapter 10, Defining the Menu, the text mentions that a \a character in the menu caption character string will right justify the text that follows it:

Page 441, Chapter 10, Defining the Menu
For items in popup menus, you can use the columnar tab character \t in the character string. Text following the \t is placed in a new column spaced far enough to the right to accommodate the longest text string in the first column of the popup. We'll see how this works when we look at keyboard accelerators toward the end of this chapter. A \a in the character string right-justifies the text that follows it.

Some clarification is needed: The \a does not merely right-justify text that follows it, it also acts as a tab character to move text the follows it into a new column. Also, the \a character does not work with the \t tab character at the same time (whether it is in the same menu item, or even in a different menu item in the same menu). They are to be used mutually exclusive of each other, in the same menu, as they both refer to two different tab columns.

Credit: Jason Doucette

 
Erratum 4: Additional Information

On page 471, Chapter 10, The Accelerator Table, the text fails to method the possibility of using the /a character, which acts as a tab character as well, except the text is right-justified.

Please note that you cannot use both the /a and /t characters within the same menu (even for different menu items), as they both refer to two different tab positions.

Credit: Jason Doucette

 
Erratum 5: Source Code Typos

On page 481, Chapter 10, Processing the Menu Options, the page shows logic used in the POPPAD2.C program, but the identifiers for each message defined in RESOURCE.H (and used in poppad2.c) differ from the ones shown here. Specifically, use the following table to replace the incorrect identifiers with the correct ones:

Page 481, Chapter 10, Processing the Menu Options
Incorrect Identifiers Corrected Identifiers
case IDM_UNDO :

case IDM_CUT :

case IDM_COPY :
 
case IDM_PASTE :

case IDM_DEL :

case IDM_SELALL :

case IDM_ABOUT :

case IDM_EXIT :
case IDM_EDIT_UNDO :

case IDM_EDIT_CUT :

case IDM_EDIT_COPY :
 
case IDM_EDIT_PASTE :

case IDM_EDIT_CLEAR :

case IDM_EDIT_SELECT_ALL :

case IDM_APP_ABOUT :

case IDM_APP_EXIT :

Credit: Jason Doucette

 
Erratum 6: Source Code Typos

On page 481, chapter 10, Processing the Menu Options, the following program code has a typo in it:

Page 481, chapter 10, Processing the Menu Options
case IDM_DEL :
     SendMessage (hwndEdit, WM_DEL, 0, 0) ;
     return 0 ;

The WM_DEL message does not exist. It should be WM_CLEAR.

Credit: Jason Doucette

 
Erratum 7: Source Code Error

On page 434, Chapter 10, Custom Resources, in the POEPOEM.C program, there is a mistake with the WM_VSCROLL message.

The switch statement in the book is like so:

switch (wParam)

It should be:

switch (LOWORD(wParam))

Credit: Jason Doucette

 
Erratum 8: Source Code Errors

On page 435, Chapter 10, Custom Resources, in the POEPOEM.C program, there is a mistake with the WM_VSCROLL message, where it handles the SB_THUMBPOSITION scroll bar value.

The book uses the following code:

iPosition = LOWORD (lParam) ;

The code should read as follows:

iPosition = HIWORD (wParam) ;

Credit: Jason Doucette

 
Erratum 9: Correction

On page 472, Chapter 10, Translating the Keystrokes, the first sentence in the third paragraph mentions:

"The hwnd parameter in TranslateMessage looks a little out of place because it's not required in the other three functions in the message loop."

It should read:

"The hwnd parameter in TranslateAccelerator looks a little out of place because it's not required in the other three functions in the message loop."

The TranslateMessage does not have a hwnd parameter, and the other three functions that do no have a hwnd parameter that the text refers to are GetMessage, TranslateMessage and DispatchMessage. TranslateAccelerator is the only function out of the four mentioned in the code example on page 471, that the text is speaking about, that has a hwnd parameter.

Credit: Kim Gräsman

 
Erratum 10: Improved Explanation

On page 428, Chapter 10, Using Customized Cursors, the text explains that the SetClassLong function (since superseded by the SetClassLongPtr function) can be used to allow the cursor to appear differently over a window. However, the text fails to mention the implications of the fact that this function changes the window class of the window handle passed in. Changing the window's class affects all windows created with this class, not just the window whose handle you passed in. If you wish to affect only one particular window, such as making a static control into a hypertext link (clickable URL) in which the cursor looks like a hand when it passes over it, then you should use the SetCursor function in response to the WM_MOUSEMOVE notification of that window. This can only be done with Window Subclassing (explained in Chapter 9, page 393), which affects only the particular window whose behaviour you wish to modify.

Credit: Jason Doucette

 
Erratum 11: Alternate Method

On page 428, Chapter 10, Using Customized Cursors, the text explains to use the WM_MOUSEMOVE Notification to perform a SetCursor function call. However, it is more precise, but no better, to use the WM_SETCURSOR Notification to perform this call. Please note the unique return value required for WM_SETCURSOR:
"If an application processes this message, it should return TRUE to halt further processing or FALSE to continue."

Credit: Jason Doucette

 
Erratum 12: Clean Up Mistake

On page 456, Chapter 10, Floating Popup Menus, in the POPMENU.C program, the WM_DESTROY handler is missing some code for proper clean up. On page 455, the LoadMenu() function is called within the WM_CREATE handler. But this menu is never destroyed with DestroyMenu() in the WM_DESTROY handler.

The MSDN documentation on DestroyMenu() states:
"Before closing, an application must use the DestroyMenu function to destroy a menu not assigned to a window. A menu that is assigned to a window is automatically destroyed when the application closes."

I believe it means that the menu is automatically destroyed when the window is destroyed. Also, I believe what is meant by 'assigned to a window' is when a menu is passed into the CreateWindow() function with the hMenu parameter, which identifies the menu to be used with the window. Note that the hMenu parameter can be NULL, which indicates that the class menu will be used. Either way, a menu is 'attached' to the window, and will be automatically destroyed when the window is destroyed.

To fix this bug, add a DestroyMenu() call as the first statement in the WM_DESTROY handler, like so:
	case WM_DESTROY:
		DestroyMenu(hMenu);
		PostQuitMessage (0) ;
		return 0 ;

Credit: Jason Doucette

 
Erratum 13: Context Menu Mistake

On page 455, Chapter 10, Floating Popup Menus, in the POPMENU.C program, Charles Petzold uses the WM_RBUTTONUP Notification to determine if the user clicked the right mouse button and released it, thus signaling the desire for a context menu to appear. However, a context menu should appear in multiple ways:
  1. The right mouse button was clicked and released.
  2. Shift+F10 was pressed.
  3. The context menu key was pressed on the keyboard.
Thus, using only WM_RBUTTONUP is improper. The proper way to handle a context menu is with the WM_CONTEXTMENU Notification. Observant readers will note that the MSDN page for WM_CONTEXTMENU states its purpose incorrectly:

"The WM_CONTEXTMENU message notifies a window that the user clicked the right mouse button (right-clicked) in the window."

The MSDN documentation is wrong! The WM_CONTEXTMENU message actually notifies a window that the user desires a context menu to appear (which could happen in any of the above mentioned ways).

Thus, we will have to replace this incorrect code:
     case WM_RBUTTONUP:
          point.x = LOWORD (lParam) ;
          point.y = HIWORD (lParam) ;
          ClientToScreen (hwnd, &point) ;
          
          TrackPopupMenu (hMenu, TPM_RIGHTBUTTON, point.x, point.y, 
                          0, hwnd, NULL) ;
          return 0 ;
with the following correct code:
     case WM_CONTEXTMENU:
          point.x = GET_X_LPARAM (lParam) ;
          point.y = GET_Y_LPARAM (lParam) ;	
          if ((point.x == -1) && (point.y == -1))
          {
               point.x = 0;
               point.y = 0;
               ClientToScreen (hwnd, &point);
          }

          TrackPopupMenu (hMenu, TPM_RIGHTBUTTON, point.x, point.y, 
                          0, hwnd, NULL) ;
          return 0 ;
You will also need to include the following line at the top of the program, which is required for the GET_X_LPARAM and GET_Y_LPARAM macros:

#include <windowsx.h>

Explanation of new code:
  • Note that the call to ClientToScreen() is no longer needed to convert the coordinates from the lParam parameter, since WM_CONTEXTMENU returns the coordinates in screen coordinates already. We do, however, use it to convert the client area coordinates (0,0) into screen coordinates within the if-statement whose purpose is explained below.

  • WM_CONTEXTMENU returns (-1,-1) for the (x,y) coordinates if the context menu was created from the keyboard, as there was no mouse click. The LOWORD and HIWORD macros return a WORD type, which is an unsigned short, which isn't good, since it doesn't allow storage of negative screen coordinates that could occur on multiple monitor systems, and it makes it complicated to test them against (-1,-1). Therefore, I replaced these with new macros that were made specifically for extracting coordinate data from the lParam parameter: GET_X_LPARAM and GET_Y_LPARAM. These return the proper signed values.

  • The WM_CONTEXTMENU documentation states:

    "If the context menu is generated from the keyboard — for example, if the user types SHIFT+F10 — then the x- and y-coordinates are -1 and the application should display the context menu at the location of the current selection rather than at (xPos, yPos)."

    Since we do not have a selection, I chose to use the the upper-left corner of the client area, which is (0,0) in client area coordinates. I use the ClientToScreen() function to convert this into the needed screen coordinates. Note that it is a bad idea to use the current position of the mouse, since the mouse could be positioned over another window, and it would appear as though the context menu is owned by the incorrect window..

Credit: Jason Doucette

 
Erratum 14:

On page 450, Chapter 10, Custom Resources, the last two lines read:
	iSelection = wParam ; 
	CheckMenuItem (hMenu, iSelection, MF_CHECKED) ;
You will note on page 447, in MENUDEMO.C, the above was quoted from the following line:
	iSelection = LOWORD(wParam) ;
The proper code is in the program, not the text. Thus, the text should be corrected as follows:
	iSelection = LOWORD(wParam) ;
	CheckMenuItem (hMenu, iSelection, MF_CHECKED) ;
Also, the text explaining the code, which is directly above it, must be changed, as well. Change the incorrect text:

"The iSelection value is set to the value of wParam, and the new background color is checked:"

to the following:

"The iSelection value is set to the low-order word of wParam, and the new background color is checked:"

Credit: Hans Dwarshuis and Jason Doucette

 
Erratum 15: Source Code Typos

On page 480, Chapter 10, Enabling Menu Items, the page shows two code statements that are used in the POPPAD2.C program, but the identifiers for each message defined in RESOURCE.H (and used in poppad2.c) differ from the ones shown here.

Specifically, change:
	EnableMenuItem (wParam, IDM_UNDO,
into:
	EnableMenuItem (wParam, IDM_EDIT_UNDO,
And, change:
	EnableMenuItem (wParam, IDM_PASTE,
into:
	EnableMenuItem (wParam, IDM_EDIT_PASTE,

Credit: Hans Dwarshuis

 
Erratum 16: Source Code Typos

On page 508, Chapter 10, The OK and Cancel Buttons, in the case statement at the bottom of the page, the following:
	case IDM_ABOUT:
should be:
	case IDM_APP_ABOUT:
You can see this proper name on page 503, in the ABOUT2.RC file.

Credit: Hans Dwarshuis

    Chapter 11 - Dialog Boxes
 
Erratum 1: Source Code Correction

On page 506, Chapter 11, Working with Dialog Box Controls, there is an explanation of the SendDlgItemMessage() function:

Page 506, Chapter 11, Working with Dialog Box Controls
SendDlgItemMessage (hDlg, id, iMsg, wParam, lParam) ;

It is equivalent to

SendMessage (GetDlgItem (hDlg, id), id, wParam, lParam) ;

Note that the second parameter (the second id in the statement) in the SendMessage() example is incorrect. It should be iMsg.

Credit: Jason Doucette

 
Erratum 2: Source Code Typo

On page 512, Chapter 11, Painting on the Dialog Box, it explains:

Page 512, Chapter 11, Painting on the Dialog Box
In AboutDlgProc, the window handle hCtrlBlock had been set during the processing of the WM_INITDIALOG message:

hCtrlBlock = GetDlgItem (hDlg, IDD_PAINT) ;

However, the control id is not IDD_PAINT. It is IDC_PAINT.

Credit: Jason Doucette

 
Erratum 3: Source Code Correction

On page 519, Chapter 11, Defining Your Own Controls, the sample code for the EllipPush window class (at the top of the page) has an error. This line:

wndclass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1) ;

should be:

wndclass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1) ;

just as it is in the source code of ABOUT3.C.

Credit: Jason Doucette

 
Erratum 4: Explanation Improvement

On page 523, Chapter 11, Differences Between Modal and Modeless Dialog Boxes, the text explains how to end a modeless dialog box with DestroyWindow(). However, it fails to mention that the parent window will destroy it when it ends, even though this is the method you use for termination with COLORS2.C, and it is implied by the explanation of COLORS2.C's message loop on pages 528 - 529.

I think it would be clearer if the text explained that the dialog box is destroyed at the end of the program, even if you do not destroy it yourself.

Credit: Jason Doucette

 
Erratum 5: Text Format Inconsistency

On page 538, Chapter 11, The Common Dialog Boxes, the last paragraph states a reference to: /Platform SDK/User Interface Services/User Input/Common Dialog Box Library. However, it is not italicized, like the rest of the references to the MSDN Library.

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: Jason Doucette

 
Erratum 6: Calculation Error

On page 490, Chapter 11, The Dialog Box and Its Template, at the end of the second paragraph on the page, the text states:

Page 490, Chapter 11, The Dialog Box and Its Template - Error
Thus, for this particular dialog box, the upper left corner of the dialog box is 5 characters from the left edge of the main window's client area and 2-1/2 characters from the top edge. The dialog itself is 40 characters wide and 10 characters high.

However, these sentences should be:

Page 490, Chapter 11, The Dialog Box and Its Template - Correction
Thus, for this particular dialog box, the upper left corner of the dialog box is 8 characters from the left edge of the main window's client area and 4 characters from the top edge. The dialog itself is 45 characters wide and 12-1/2 characters high.

As explained at the start of the same paragraph in the text: "The numbers are based on the size of the font used for the dialog box (in this case, an 8-point MS Sans Serif font): x-coordinates and width are expressed in units of 1/4 of an average character width; y-coordinates and height are expressed in units of 1/8 of the character height."

The dialog template in question is shown below, with the coordinates in question bolded:

ABOUTBOX DIALOG DISCARDABLE  32, 32, 180, 100
STYLE DS_MODALFRAME | WS_POPUP
FONT 8, "MS Sans Serif"

BEGIN
    DEFPUSHBUTTON   "OK",IDOK,66,80,50,14
    ICON            "ABOUT1",IDC_STATIC,7,7,21,20
    CTEXT           "About1",IDC_STATIC,40,12,100,8
    CTEXT           "About Box Demo Program",IDC_STATIC,7,40,166,8
    CTEXT           "(c) Charles Petzold, 1998",IDC_STATIC,7,52,166,8
END

The first two coordinates (32, 32) are the location of top-left corner of the dialog box in respect to the window's client area's top-left corner. The x coordinate is 32 units from the edge. With a unit equaling 1/4 of an average character width, it is 32 * 1/4 = 8 characters from the edge. Likewise, the y coordinate is 32 units from the edge. With a unit equaling 1/8 of an average character width, it is 32 * 1/8 = 4 characters from the edge.

The next two coordinates (180, 100) specify the size of the dialog box. The width coordinate is 180 units. With a unit equaling 1/4 of an average character width, it is 180 * 1/4 = 45 characters wide. Likewise, the height coordinate is 100 units. With a unit equaling 1/8 of an average character width, it is 100 * 1/8 = 12-1/2 characters high.

Credit: John Kopplin

 
Erratum 7: Error

On page 496, Chapter 11, Variations on a Theme, the table at the top of the page shows the following two default window styles:

Control Type Window Class Window Style
GROUPBOX button BS_GROUPBOX | WS_TABSTOP
LISTBOX listbox LBS_NOTIFY | WS_BORDER | WS_VSCROLL

However Microsoft's documentation for these controls state that if you do not specify any window style then you will get only the following:

Control Type Window Class Window Style
GROUPBOX button BS_GROUPBOX
LISTBOX listbox LBS_NOTIFY | WS_BORDER

Credit: John Kopplin

 
Erratum 8: Error

On page 518, Chapter 11, Defining Your Own Controls, the statement at the bottom of the page employs the style TABGRP which does not seem to be defined anywhere:

CONTROL "OK" IDOK, "EllipPush", TABGRP, 64, 60, 32, 14

It is probably intended to mean WS_TABSTOP | WS_GROUP, as the text shows on page 517, in ABOUT3.RC (excerpts):

ABOUTBOX DIALOG DISCARDABLE  32, 32, 180, 100
STYLE DS_MODALFRAME | WS_POPUP
FONT 8, "MS Sans Serif"
BEGIN
    CONTROL         "OK",IDOK,"EllipPush",WS_GROUP | WS_TABSTOP,73,79,32,14
    ICON            "ABOUT3",IDC_STATIC,7,7,20,20
    CTEXT           "About3",IDC_STATIC,40,12,100,8
    CTEXT           "About Box Demo Program",IDC_STATIC,7,40,166,8
    CTEXT           "(c) Charles Petzold, 1998",IDC_STATIC,7,52,166,8
END
A search on Google reveals that in most cases, when a programmer uses TABGRP, they define it as a macro like so:

#define TABGRP (WS_TABSTOP | WS_GROUP)

This is consistent with the styles used in the code.

Credit: John Kopplin

 
Erratum 9: Typo

On page 564, Chapter 11, Search and Replace, the first paragraph makes a reference to POPFIND.C in Figure 10-11. This should be Figure 11-11.

Credit: John Kopplin

 
Erratum 10: Further Explanation

On page 562, Chapter 11, POPPAD Revisited, the book introduces the OPENFILENAME Structure. It explains that the program POPPAD.C sets the hwndOwner field of the structure to be the window who owns the dialog box to be made. However, neither Charles Petzold nor MSDN states what potential problems may occur if you set this field to be NULL. NULL indicates that the dialog box has no owner.

If the dialog box has no owner, it acts like a modeless dialog box, rather than a modal dialog box. This has the unfortunate side effect that the user can interact with the program's main window just as if the dialog box did not exist. A user could even request the application to shut down while the dialog box is still open. It is therefore a good idea to always give a File Open or File Save dialog box an owner.

Credit: Jason Doucette

 
Erratum 11: Cleaner Code

On page 522, Chapter 11, Differences Between Model and Modeless Dialog Boxes, the book explains how to modify the message loop for handling messages of a modeless dialog box. In both code sections on this page, the book uses the following comparison to see if the modeless dialog box is currently active:

hDlgModeless == 0

While this code works perfectly, the MSDN team recommends a different method, by using the IsWindow() function:

!IsWindow(hDlgModeless)

To ensure clarity, making this change will result in the following full line of code in both code segments:

if (!IsWindow(hDlgModeless) ¦¦ !IsDialogMessage (hDlgModeless, &msg))

This use of the IsWindow() function will replace the original example code in any of the places that it appears elsewhere in the book. Please note that it may exist in one of three different formats, which all mean the exact same thing:
  • hDlgModeless == 0
  • hDlgModeless == NULL
  • !hDlgModeless
NULL is defined as 0. It is the value of a pointer that points to nothing. Therefore, comparing a handle to 0 or NULL is the same thing; you are asking whether or not the handle points to an object that exists or not. The Logical-NOT operator (!), when applied to a pointer whose value is 0 (equivalent to false), will return true. If applied to a pointer whose value is not equal to 0 (equivalent to true), it will return false. Therefore, this is identical to merely comparing the pointer to NULL or 0.

Please note that this original, in one of its three forms, exists elsewhere in the book in the following locations:
  • Page 525, Chapter 11, in the COLORS2.C program
  • Page 541, Chapter 11, in the POPPAD.C program
  • Page 630, Chapter 13, in the PRINT3.C program
  • Page 632, Chapter 13, Adding a Printing Dialog Box
  • Page 634, Chapter 13, in the POPPRNT.C program
  • Page 1010, Chapter 17, in the PICKFONT.C program
I have not included an erratum for each of these cases in this list, because the original code is not wrong. This erratum merely explains, what the MSDN team believes to be, a nicer solution. As you can see, even if you do not use the IsWindow() function, there are at least three methods that Charles Petzold himself uses to accomplish the same task. It is really personal preference, although it is desirable to have some consistency in your code. Also, the IsWindow() may be much clearer to some, especially to other programmers that may read your code.

Credit: Jason Doucette

 
Erratum 12: Typo

On page 505, Chapter 11, Working with Dialog Box Controls, the book states:

"You might recall from Chapter 9 that checking and unchecking a button requires that you send the child window control a BM_CHECK message."

There is no BM_CHECK message. The actual message name is BM_SETCHECK, as explained in Chapter 9, on pages 366 to 369.

Credit: Jason Doucette

 
Erratum 13: Implementation Error

On page 523, Chapter 11, Differences Between Modal and Modeless Dialog Boxes, and identically on page 529, Chapter 11, The New COLORS Program, the book shows the following code sample:

case WM_CLOSE :
     DestroyWindow (hDlg) ;
     hDlgModeless = NULL ;
     break ;
This shows an example how to handle the WM_CLOSE message for a modeless dialog box. (The WM_CLOSE message is sent to the dialog box procedure when the 'Close' option from the system menu is selected, the close 'x' button is pressed, or the user presses ALT+F4 when the dialog box is in focus.) You need not always handle the WM_CLOSE message, as there is default processing available if you do not handle it. As explained on the Dialog Box Programming Considerations MSDN page, its default processing is as follows:

"(It) posts the BN_CLICKED notification message to the dialog box, specifying IDCANCEL as the control identifier. If the dialog box has an IDCANCEL control identifier and the control is currently disabled, the procedure sounds a warning and does not post the message."

This basically states that if you let the dialog box procedure handle the WM_CLOSE message itself, the default action is to simulate a button press of the 'Cancel' button (which has the IDCANCEL control identifier by default when designing a dialog box in the resource editor). Likely, the processing of the 'Cancel' button press, which you must code, destroys the dialog box.

The first paragraph of text on the Dialog Box Programming Considerations MSDN page states: "a dialog box procedure ... returns TRUE if it processes a message or FALSE if it does not." However, this is not exactly true, although you should abide by it. Whether you return TRUE or FALSE indicates whether the default processing of the message will be carried out. If you do not process the message, you obviously wish for the default processing to be executed, so you return FALSE. However, if you do process the message, you may or may not want the default processing to occur. It is highly likely that if you process the message, you do not want the default processing to occur, and thus you return TRUE. There is little reason to allow the default processing to occur if you are doing your own processing; this is why the text states what it does.

Getting back to the code sample from the book, you will notice it takes charge and destroys the window on its own. However, because it calls the break statement, instead of returning TRUE, the switch statement control ends. This ends up returning FALSE. Why? Because all messages that are not handled by the switch statement are not processed, and you have to let the dialog box procedure do its default handling for these messages. You do that by returning FALSE for any message that passed through the switch statement. Because the example WM_CLOSE handler decides to break out of the switch statement, it is subjected to the same result - returning FALSE, which means the default processing will be carried out. This does not make much sense, since the default processing simulates a press of the 'Cancel' button (or, more specifically, the button whose control identifier is IDCANCEL). Since the 'Cancel' button handler, if it exists in the code that this example may be used in, will handle the destruction of the dialog box, it makes little sense for the WM_CLOSE handler to do it. On the other hand, if there is no 'Cancel' button, then why would you want the WM_CLOSE handler to tell the dialog box procedure to attempt to press it?

Thus, the proper solution in this code sample is to not allow the default processing, and to tell the dialog box procedure that you have properly handled the message yourself. The proper code is as follows:

case WM_CLOSE :
     DestroyWindow (hDlg) ;
     hDlgModeless = NULL ;
     return TRUE ;

Credit: Jason Doucette

 
Erratum 14: Incorrect Explanation

On page 483, Chapter 11, Dialog Boxes, the second sentence states:

"The programmer indicates that a menu item invokes a dialog box by adding an ellipsis (...) to the menu item."

However, this is not necessarily so. Raymond Chen explains the semantics of "..." (ellipses) in one of his articles:

When do you put ... after a button or menu?
"Use an ellipsis if the command requires additional information before it can be performed. Sometimes the dialog box is the command itself, such as "About" or "Properties". Even though they display a dialog, the dialog is the result, as opposed to commands like "Print" where the dialog is collecting additional information prior to the result."

Credit: Kim Gräsman

 
Erratum 15: Correction

On page 483, Chapter 11, Dialog Boxes, the first sentence of the third paragraph states:

"When a program invokes a dialog box based on a template, Microsoft Windows 98 is responsible for..."

However, this is true for all versions of Windows, not just Windows 98. Therefore, it should have simply stated "...Windows is responsible for...".

Credit: Kim Gräsman

 
Erratum 15: Typo

On page 564, Chapter 11, Unicode File I/O, the first sentence on the page states:

"Similarly, if the file is a non-Unicode text file but the Unicode version of the program is running, the text must be converted using MultiCharToWideChar."

However, there is no MultiCharToWideChar function. It should be MultiByteToWideChar.

Credit: Kim Gräsman

 
Erratum 17: Inconsistent Name

On page 518, Chapter 10, Defining Your Own Controls, the file ABOUT3.C uses the icon name "icon1.ico". However, the first two programs used “about1.ico” and “about2.ico”, so following suit, it should be “about3.ico”. On page 518, the icon image itself is shown, and Petzold calls it “ABOUT3.ICO”.

The icon filename on the source CD that accompanies the book is called “icon1.ico”. If it was named differently, the program would not compile. So, if you change the above to be "about3.ico", and you are using the icon file from the source CD, you will need to rename it to “about3.ico”, as well.

Please note that the filename is arbitrary. As long as the name in the .RC file matches the actual the filename, all is well.

Credit: Hans Dwarshuis and Jason Doucette

 
Erratum 18: Miscalculation

On page 537, Chapter 11, HEXCALC: Window or Dialog Box?, the fourth line of code shows:
	102 * 4 / cxChar, 122 * 8 / cyChar,
This line should be:
	102 * cxChar / 4, 122 * cyChar / 8,
You can see this same formula used at the bottom of page 497. On page 496, it says to refer to chapter 9 where the sizes specified in dialog box templates are 1/4 and 1/8 of the average character width and height respectively, which explains these formulas.

Credit: Hans Dwarshuis

 
Erratum 19: Minor Mistype

On page 581, Chapter 11, BEYOND SIMPLE CLIPBOARD USE, the first line reads:

"We’ve seen that transferring text from the clipboard requires four calls after the data has been prepared:"

It should be:

"We’ve seen that transferring text to the clipboard requires four calls after the data has been prepared:"

It is referring to the section that starts on page 572, which explains how to transfer text to the clipboard.

Credit: Hans Dwarshuis

    Chapter 12 - The Clipboard
 
Erratum 1: Source Code Consistency

On page 571, Chapter 12, Memory Allocation, an explanation starts in the middle of the page on how to allocate movable memory:

Page 571, Chapter 12, Memory Allocation
First define a pointer (for example, to an int type) and a variable of type GLOBALHANDLE:

     int * p ;
     GLOBALHANDLE hGlobal ;
Then allocate the memory. For example:

hGlobal = GlobalAlloc (GHND, 1024) ;

However, GlobalAlloc() returns an HGLOBAL type, not GLOBALHANDLE. Also, in the source code for the CLIPTEXT.C example program that follows this, hGlobal is declared as type HGLOBAL, not GLOBALHANDLE:

Page 577, Chapter 12, CLIPTEXT.C
HGLOBAL hGlobal ;

The code compiles with either, as both HGLOBAL and GLOBALHANDLE are typedef'ed as HANDLE, but it would make more sense to maintain consistency with the source code sample in the text, the source code of the example program, and the MSDN library.

Credit: Jason Doucette

 
Erratum 2: Source Code Omission

On page 578, Chapter 12, The Clipboard and Unicode, in the CLIPTEXT.C program, there is missing GlobalUnlock() call during processing of the IDM_EDIT_PASTE case of the WM_COMMAND message. The following line of code should be inserted immediately before the InvalidateRect() call:

GlobalUnlock (hGlobal) ;

Credit: Doug Yip

 
Erratum 3: Typo

On page 572, Chapter 12, Transferring Text to the Clipboard, the example code showing how to copy an ANSI character string to the clipboard uses a loop to copy each individual character from the string into the locked memory block:

for (i = 0 ; i < wLength ; i++)
     *pGlobal++ = *pString++ ;
The wLength variable should be iLength, as mentioned in the first paragraph of this section.

Credit: Jason Doucette

Section
II

More
Graphics
    Chapter 13 - Using the Printer
 
Erratum 1: Resource File Correction

On page 605, Chapter 13, The Revised DEVCAPS Program, the program DEVCAPS2.C is used to display device capability information for the screen and any printers hooked up to the system. The first menu, "Device", selects the device, and the second menu, "Capabilities", selects what capabilities to display. The currently selected capability for this menu is checked. However, at the start of the program, none of the menu items within that menu are checked (even though the first one is activated by default). The program should check the first menu item on start up.

This can be done in a couple of different ways. The easiest way is to go into the resource editor, double click the "Basic Information" menu item under the "Capabilities" menu, and check the "Checked" button, as shown in the following image:



This will set this menu item to be checked at program start up.

If you wish to set this by manually changing the DEVCAPS2.RC resource file, shown on page 615, you will have to add the text ", CHECKED" to the end of the line that describes the "Basic Information" menu item, as shown in the following code:

Page 615, Chapter 13, DEVCAPS2.RC (excerpts) - Correction
DEVCAPS2 MENU DISCARDABLE 
BEGIN
    POPUP "&Device"
    BEGIN
        MENUITEM "&Screen",                     IDM_SCREEN, CHECKED
    END
    POPUP "&Capabilities"
    BEGIN
        MENUITEM "&Basic Information",          IDM_BASIC, CHECKED
        MENUITEM "&Other Information",          IDM_OTHER
        MENUITEM "&Curve Capabilities",         IDM_CURVE
        MENUITEM "&Line Capabilities",          IDM_LINE
        MENUITEM "&Polygonal Capabilities",     IDM_POLY
        MENUITEM "&Text Capabilities",          IDM_TEXT
    END
END

A third method to solve this problem is to add code to the WM_CREATE message handling code to set this value to be checked. This is not standard procedure, but I will show you how it is done, regardless. At the end of the WM_CREATE message handling code on page 607, add the following:

Page 607, Chapter 13, DEVCAPS2.C - Additional Code
          hMenu = GetMenu (hwnd) ;
          CheckMenuItem (hMenu, nCurrentInfo, MF_CHECKED) ;

The code retrieves the handle to the menu assigned to hwnd with the GetMenu() function, and uses this as a parameter into the CheckMenuItem() function to set the state of menu item nCurrentInfo check mark attribute to checked.

Credit: Jason Doucette

 
Erratum 2: Code Clarification

On page 621, Chapter 13, PRINTING GRAPHICS AND TEXT, the source code listing for PRINT.C has the following statement in the WM_CREATE message handling code:

AppendMenu (hMenu, 0, 1, TEXT ("&Print")) ;

It uses 0 as the second parameter, which is equivalent to MF_STRING. The code should use MF_STRING to make it clear on what it is doing, as follows:

AppendMenu (hMenu, MF_STRING, 1, TEXT ("&Print")) ;

Credit: Jason Doucette

 
Erratum 3: Potential Bug

On page 621, Chapter 13, PRINTING GRAPHICS AND TEXT, the source code listing for PRINT.C has a potential bug in the WM_SYSCOMMAND message handling code:

     case WM_SYSCOMMAND:
          if (wParam == 1)
          {
               ...

Since the menu item we added was to the window menu (formerly known as the system or control menu), we must process the WM_SYSCOMMAND message. However, the MSDN page for WM_SYSCOMMAND states:

MSDN page for WM_SYSCOMMAND
In WM_SYSCOMMAND messages, the four low-order bits of the wParam parameter are used internally by the system. To obtain the correct result when testing the value of wParam, an application must combine the value 0xFFF0 with the wParam value by using the bitwise AND operator.

It is not clear that this is required for user created menu items, however I have not seen any evidence that shows any problems with using user create menu item identification numbers that are not multiples of 16, even when using keyboard accelerators. It may be wise to use identification numbers that are multiples of 16, and to AND the wParam value within WM_SYSCOMMAND with 0xFFF0, just in case. This can be done as follows:

     case WM_SYSCOMMAND:
          if ((wParam & 0xFFF0) == 1)
          {          
               ...

Credit: Jason Doucette

 
Erratum 4: Source Code Error

On page 638, Chapter 13, Adding Printing to POPPAD, in the middle of the page, there is an explanation regarding when EndDoc() is called:

Page 638, Chapter 13, Adding Printing to POPPAD
The program breaks from the for loop incrementing the page number if either StartPage or EndPage returns an error or if bUserAbort is TRUE. If the return value of the abort procedure is FALSE, EndPage doesn't return an error. For this reason, bUserAbort is tested explicitly before the next page is started. If no error is reported, the call to EndDoc is made:

if (!bError)
     EndDoc (hdcPrn) ;

This does not match the code supplied in the text. hdcPrn does not exist in the code, anywhere. The code supplied in the text is as follows, which is correct:

     if (bSuccess)
          EndDoc (pd.hDC) ;

Credit: Jason Doucette

 
Erratum 5: Text Typo

On page 638, Chapter 13, Adding Printing to POPPAD, the text mentions SetAbortDoc in the first symbol in the flow chart diagram. I can find no reference to a function of this name anywhere, except as one of the required printer driver functions, which are called by Windows GDI that all Windows printers must support. I think Charles Petzold meant to write SetAbortProc.

Credit: Jason Doucette

 
Erratum 6: Text Typo

On page 603, Chapter 13, The Printer Device Context, the word 'job' near the end of the first paragraph should be 'call'.

Credit: John Kopplin

 
Erratum 7: Source Code Typo

On page 634, Chapter 13, POPPRNT.C, there is a variable named iNoiColCopy referenced several times:

Page 634, Chapter 13, POPPRNT.C
...
BOOL PopPrntPrintFile (HINSTANCE hInst, HWND hwnd, HWND hwndEdit, 
                       PTSTR szTitleName)
{
     static DOCINFO  di = { sizeof (DOCINFO) } ;
     static PRINTDLG pd ;
     BOOL            bSuccess ;
     int             yChar, iCharsPerLine, iLinesPerPage, iTotalLines,
                     iTotalPages, iPage, iLine, iLineNum ;
     PTSTR           pstrBuffer ;
     TCHAR           szJobName [64 + MAX_PATH] ;
     TEXTMETRIC      tm ;
     WORD            iColCopy, iNoiColCopy ;     
     ...
Page 636, Chapter 13, POPPRNT.C
...
if (StartDoc (pd.hDC, &di) > 0)
{
          // Collation requires this loop and iNoiColCopy

     for (iColCopy = 0 ;
          iColCopy < ((WORD) pd.Flags & PD_COLLATE ? pd.nCopies : 1) ;
          iColCopy++)
     {
          for (iPage = 0 ; iPage < iTotalPages ; iPage++)
          {
               for (iNoiColCopy = 0 ;
                    iNoiColCopy < (pd.Flags & PD_COLLATE ? 1 : pd.nCopies);
                    iNoiColCopy++)
               {
                    ...

In each of these cases, the variable should be called iNonColCopy, as mentioned on page 638, in the fourth paragraph. iColCopy is the variable for collated copies, and iNonColCopy is the variable for non-collated copies.

Credit: John Kopplin

 
Erratum 8: Source Code Bug

On page 604, Chapter 13, GETPRNDC.C, the function GetPrinterDc() has a bug. You should first notice the large if-then-else block that splits the entire function into two separate, but logically identical, sections:
  • one for Windows 98 (Windows 95, Windows 98, Windows Me, or Win32s with Windows 3.1)
  • one for Windows NT (Windows NT 3.51, Windows NT 4.0, Windows 2000 or Windows XP)
Notice that both code sections exercise the same logic, but merely use two different structures. The bug is not caused by the structure use, so the bug is repeated in both code sections.

In each code section, the function uses the same logic by making two calls to the EnumPrinters() function. The first call passes EnumPrinters() a buffer size of 0 in which to store its information, which causes it to return a value in dwNeeded that indicates how much memory is required for its buffer. In the 'Windows 98' section, this will be a multiple of the size of the PRINTER_INFO_5 structure. In the 'Windows NT' section, this will be a multiple of the size of the PRINTER_INFO_4 structure. Once this value is known, we can dynamically allocate a block of memory this size, and then re-call the function with this new memory block. The second call to EnumPrinters() passes a buffer size of dwNeeded (the size the first call requested to have), and the location of this buffer. This call will fill this memory block with printer information. The dwReturned will store the number of structures stored in the buffer (essentially, the number of printers we enumerated).

So, where is the bug? The problem appears when there are no printers installed. The CreateDC() call will fail when passed a member variable dereferenced from a pointer that points to a block of memory zero bytes in size. It will attempt to access memory that it does not have the right to access. We know when there are 0 printers, because dwReturned will return a value of 0, indicating that there are no structures in the allocated memory block. We could merely check to see if this is 0, and set hdc to NULL instead of setting it to the return value of CreateDC() (we cannot merely quit the function by returning NULL, as we still have to clean up our allocated memory). This will cause GetPrinterDC() to return NULL when there are no printers, which all of the programs that call GetPrinterDC() (FORMFEED.C, PRINT1.C, PRINT2.C, and PRINT3.C) handle correctly.

However, this solution has the ugly side effect that the original EnumPrinters() call would have determined that you would need to allocate 0 bytes of memory for 0 structures. Therefore, we can instead check the dwNeeded variable after the first EnumPrinters() call, and check it against 0. Although this variable is not the number of printers available, it is the number of printers available multiplied by the size of the structure the function wishes to fill. If the system has 0 available printers, 0 times any structure size equals 0. So, we can solve the bug easily by adding this line of code after the first EnumPrinters() call in both code sections:

if (dwNeeded == 0) return(NULL); // return NULL if there are no printers.

You will notice that we can quit the function immediately, as there are no memory allocations we need to clean up at this point.

Credit: Jason Doucette

    Chapter 14 - Bitmaps and Bitblts
 
Erratum 1: Text Correction

On page 646, Chapter 14, Real-World Devices, at the top of the page, it states:

Page 646, Chapter 14, Real-World Devices
To display 256 colors, the original VGA had to be switched into a 320 by 240 graphics mode, which is an inadequate number of pixels for Windows to work properly.

However, the original VGA only documented mode 13h, which is a 320 by 200, 256 colors, 70 Hz video mode. The 320 x 240, 256 colors, 60 Hz video mode is an uncommon, undocumented, tweaked video mode that is normally referred to as Mode-X (other tweaked video modes are also referred to as Mode-X, as well as Mode-Y and Mode-Q).

Credit: Jason Doucette

 
Erratum 2: Source Code Correction

On page 654, Chapter 14, Stretching the Bitmap, the program STRETCH.C calls StretchBlt() as follows:

Page 654, Chapter 14, Stretching the Bitmap - Error
          StretchBlt (hdcClient, 0, 0, cxClient, cyClient,
                      hdcWindow, 0, 0, cxSource, cySource, MERGECOPY) ;

This program is supposed to show off what StretchBlt() can do before getting into raster-operation codes, as page 657 explains:

Page 657, Chapter 14, The Raster Operations
The BITBLT and STRETCH programs simply copy the source bitmap to the destination, perhaps stretching it in the process. This is the result of specifying SRCCOPY as the last argument to the BitBlt and StretchBlt functions.

Obviously, the StretchBlt() call in STRETCH.C was supposed to use SRCCOPY as the last parameter:

Page 654, Chapter 14, Stretching the Bitmap - Correction
          StretchBlt (hdcClient, 0, 0, cxClient, cyClient,
                      hdcWindow, 0, 0, cxSource, cySource, SRCCOPY) ;

Credit: Jason Doucette

 
Erratum 3: Improper Source Code File on Companion CD

In the source code for Programming Windows, 5th Edition, under the \Chap14\Bounce directory, there is a BOUNCE.CPP file that is empty (0 bytes). I know that all of the code is made in C, so there should not be any .CPP files. Perhaps this was a file created by mistake, and simply was not deleted.

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: Jason Doucette

 
Erratum 4: Source Code Errors

On page 714, Chapter 14, Bitmaps Outside the Window, the program BLOWUP.C has many bugs all based on the same cause. They all result from the following messages: WM_RBUTTONDOWN, WM_MOUSEMOVE, WM_LBUTTONUP and WM_RBUTTONUP:

Page 717 - 718, Chapter 14, BLOWUP.C - Error
     case WM_RBUTTONDOWN:
          if (bCapturing)
          {
               bBlocking = TRUE ;
               ptBeg.x = LOWORD (lParam) ;
               ptBeg.y = HIWORD (lParam) ;
               ptEnd = ptBeg ;
               InvertBlock (hwndScr, hwnd, ptBeg, ptEnd) ;
          }
          return 0 ;

     case WM_MOUSEMOVE:
          if (bBlocking)
          {
               InvertBlock (hwndScr, hwnd, ptBeg, ptEnd) ;
               ptEnd.x = LOWORD (lParam) ;
               ptEnd.y = HIWORD (lParam) ;
               InvertBlock (hwndScr, hwnd, ptBeg, ptEnd) ;
          }
          return 0 ;

     case WM_LBUTTONUP:
     case WM_RBUTTONUP:
          if (bBlocking)
          {
               InvertBlock (hwndScr, hwnd, ptBeg, ptEnd) ;
               ptEnd.x = LOWORD (lParam) ;
               ptEnd.y = HIWORD (lParam) ;


The MSDN with MSVC++ 6.0 incorrectly states for each of these commands that the following is true:

xPos = LOWORD(lParam);  // horizontal position of cursor
yPos = HIWORD(lParam);  // vertical position of cursor
The program BLOWUP.C assumes that the MSVC++ 6.0 MSDN is correct, and uses these macros. However, this is faulty, as evident when the program is run, since the coordinates can be negative for each of these cases. Both LOWORD and HIWORD return WORD values, which is typedef'ed to unsigned short. The online MSDN fixes the comments, and states that the GET_X_LPARAM and GET_Y_LPARAM macros should be used:

xPos = GET_X_LPARAM(lParam);
yPos = GET_Y_LPARAM(lParam);
These two macros are available in the windowsx.h file. They are defined as follows:
#define GET_X_LPARAM(lp)  ((int)(short)LOWORD(lp))
#define GET_Y_LPARAM(lp)  ((int)(short)HIWORD(lp))
Therefore, to correct BLOWUP.C, you must include the windowsx.h file as follows:

#include <windowsx.h>
Or define these macros within the code yourself. Also, you must fix the three sections of code for the four messages mentioned above (WM_RBUTTONDOWN, WM_MOUSEMOVE, WM_LBUTTONUP and WM_RBUTTONUP) which extract the mouse position from lParam, as follows:

Page 717 - 718, Chapter 14, BLOWUP.C - Correction
     case WM_RBUTTONDOWN:
          if (bCapturing)
          {
               bBlocking = TRUE ;
               ptBeg.x = GET_X_LPARAM(lParam) ;
               ptBeg.y = GET_Y_LPARAM(lParam) ; 
               ptEnd = ptBeg ;
               InvertBlock (hwndScr, hwnd, ptBeg, ptEnd) ;
          }
          return 0 ;

     case WM_MOUSEMOVE:
          if (bBlocking)
          {
               InvertBlock (hwndScr, hwnd, ptBeg, ptEnd) ;
               ptEnd.x = GET_X_LPARAM(lParam) ;
               ptEnd.y = GET_Y_LPARAM(lParam) ; 
               InvertBlock (hwndScr, hwnd, ptBeg, ptEnd) ;
          }
          return 0 ;

     case WM_LBUTTONUP:
     case WM_RBUTTONUP:
          if (bBlocking)
          {
               InvertBlock (hwndScr, hwnd, ptBeg, ptEnd) ;
               ptEnd.x = GET_X_LPARAM(lParam) ; 
               ptEnd.y = GET_Y_LPARAM(lParam) ; 


There is another possibility, without including windowsx.h or defining the macros, and that is to use the MAKEPOINTS macro to convert the lParam parameter to a POINTS structure. This solution is not so great, as most programs use the POINT structure (without the S at the end), which means you must convert it before it can be used. The difference between the two, as defined in WINDEF.H, are as follows:

typedef struct tagPOINT
{
    LONG  x;
    LONG  y;
} POINT;

typedef struct tagPOINTS
{
    SHORT   y;
    SHORT   x;
} POINTS;
I recommended including windowsx.h, and using the GET_X_LPARAM and GET_Y_LPARAM macros.

Credit: Jason Doucette

 
Erratum 5: Source Code Omission

On page 719, Chapter 14, Bitmaps Outside the Window, in the BLOWUP.C program, there is a major bug with the clipboard operations. For the IDM_EDIT_CUT and IDM_EDIT_COPY cases, there is a missing CloseClipboard() call. Please insert it immediately after the SetClipboardData() call to fix the bug, as follows:

CloseClipboard();

Credit: Jason Doucette

 
Erratum 6: Source Code Omission

On page 657, Chapter 14, The Raster Operations, there is a missing SelectObject() call within the DeleteObject() call. Please change the following statement:

DeleteObject (hdcClient, GetStockObject (WHITE_BRUSH)) ;

To this statement:

DeleteObject (SelectObject (hdcClient, GetStockObject (WHITE_BRUSH))) ;

Do not forget the extra closing parenthesis at the end.

Credit: Paul Levijoki

 
Erratum 7: Typo

On page 707, Chapter 14, Nonrectangular Bitmap Images, the first sentence on the page states:

"If you're writing applications specifically for Windows NT, you can use the MaskBlt function to do something similar to the MASKBIT program with fewer function calls."

However, the program Charles Petzold is referring to is his BITMASK program. The code for this program commences on page 702.

Credit: Kim Gräsman

 
Erratum 8: Typo

On page 663, Chapter 14, Creating a DDB, the following line of code, about two thirds down the page, is missing a parenthesis:
	for iWidthBytes = (cx * cBitsPixel + 15) & -15) >> 3 ;
It should be changed to:
	for iWidthBytes = ((cx * cBitsPixel + 15) & -15) >> 3 ;

Credit: Hans Dwarshuis

 
Erratum 9: Typo

On page 664, Chapter 14, Creating a DDB, the second line of code on the page is missing an equals sign:
hBitMap CreateBitmapIndirect (&Bitmap) ;
It should appear as follows:
hBitMap = CreateBitmapIndirect (&Bitmap) ;

Credit: Hans Dwarshuis

 
Erratum 10: Inconsistent Code

On page 699, Chapter 14, Using Bitmaps in Menus, the majority of the page and a bit of the next explains the code in the StretchBitmap function in the GRAFMENU.C program on page 693. However, the explanation code is inconsistent with the program code.

On page 693, the StretchBitmap function in the GRAFMENU.C program code uses two variables cxChar and cyChar. They are initialized with the return values of the GetDialogBaseUnits() Function. This function returns the average width and height of characters in the system font. You can see these two variables values being used inside a formula to set bm2.bmWidth and bm2.bmHeight.

On page 699, cxChar and cyChar are not used. Instead, a variable called tm of type TEXTMETRIC is initialized with the return value of the GetTextMetrics function. This gets the average character width and the height of characters of the currently selected font of the device context passed to it (in this case, hdc). These two values are used in the same formula as cxChar and cyChar were.

So, both code sections are correct. They are just inconsistent.

Credit: Hans Dwarshuis and Jason Doucette

 
Erratum 11: Inconsistent Code

On page 700, Chapter 14, Using Bitmaps in Menus, almost the entire page explains the inner workings of GRAFMENU.C's CreateMyMenu() function. You will noticed that the AppendMenu() Function is used 3 times. In all 3 cases, the 3rd parameter is hMenuPopup. In the CreateMyMenu() function, hMenuPopup is type cast into an int, but in the code on page 700, it is not type cast into an int. This is not an issue, since this book was written for C. The C programming language will cast the parameter to the proper value implicitly.

Regarding the issue of type casting, you should be aware of the C++ typecasting issues and 64-bit portability issues elsewhere in this errata list.

Credit: Hans Dwarshuis and Jason Doucette

 
Erratum 12: Code Inconsistency

On page 701, Chapter 14, Using Bitmaps in Menus, the second and fourth lines of code uses the variable hBitmapHelp. This portion of the text is covering the code in GRAFMENU.C's AddHelpToSys() function, on page 692. This function uses the variable name hBitmap, without the 'Help' postfix. Of course, both sections of code are correct. As long as you use the same variable name throughout, the code will work. But, since the text is explaining the program's code, it should be consistent with it.

Credit: Hans Dwarshuis and Jason Doucette

    Chapter 15 - The Device-Independent Bitmap
 
Erratum 1: Typo

On page 735, Chapter 15, DIB Compression, the sentence before the table states BI_RGB8. This should be BI_RLE8.

Credit: Jason Doucette

 
Erratum 2: Typo

On page 741, Chapter 15, Version 4 Header, the second to last paragraph mentions BITMAPV5HEADER. However, this version of the header is not explained until page 744. It should be BITMAPV4HEADER.

Credit: Jason Doucette

 
Erratum 3: Typo

On page 742, Chapter 15, The Version 4 Header, at the bottom, the text refers to the bV4CSType field of the BITMAPV5HEADER structure. This should be BITMAPV4HEADER.

Credit: Jason Doucette

 
Erratum 4: Changed URL

On page 745, Chapter 15, The Version 5 Header, the first paragraph mentions that the website of International Color Consortium (founded by Adobe, Agfa, Apple, Kodak, Microsoft, Silicon Graphics, Sun Microsystems, and others) is http://www.icc.org. However, this website now belongs to the Internet Chamber of Commerce. The International Color Consortium website is now http://www.color.org/.

Credit: Jason Doucette

 
Erratum 5: Updated URL

On page 745, Chapter 15, The Version 5 Header, the last paragraph shows a URL which is no longer valid: http://www.color.org/contrib/sRGB.html. Information regarding sRGB can be found at the following website: http://www.srgb.com/srgb.html.

Credit: Jason Doucette

 
Erratum 6: Source Code Comment Error

On page 772, Chapter 15, The Topsy-Turvy World of DIBs, the first comments in the APOLLO11.C program state:

Page 772, Chapter 15, APOLLO11.C
/*----------------------------------------------
   APOLLO11.C -- Program for screen captures
                 (c) Charles Petzold, 1998
  ----------------------------------------------*/

Since this program is not intended for screen captures (it is for showing how to display DIBs properly), this comment is in error.

Credit: Jason Doucette

 
Erratum 7: Typo

On page 725, Chapter 15, The OS/2-Style DIB, the definition of the BITMAPFILEHEADER struct refers to the final member as .bfOffsetBits but its name is actually .bfOffBits.

Credit: John Kopplin

 
Erratum 8: Source Code Logic Error

On page 747, Chapter 15, DIBHEADS.C, the source code uses the following function to append text into an edit control:

Page 747, Chapter 15, DIBHEADS.C - Error
void Printf (HWND hwnd, TCHAR * szFormat, ...)
{
     TCHAR   szBuffer [1024] ;
     va_list pArgList ;

     va_start (pArgList, szFormat) ;
     wvsprintf (szBuffer, szFormat, pArgList) ;
     va_end (pArgList) ;

     SendMessage (hwnd, EM_SETSEL, (WPARAM) -1, (LPARAM) -1) ;
     SendMessage (hwnd, EM_REPLACESEL, FALSE, (LPARAM) szBuffer) ;
     SendMessage (hwnd, EM_SCROLLCARET, 0, 0) ;
}

The logic in the first two SendMessage() calls has a flaw. An application sends an EM_SETSEL Message to select a range of characters in an edit control. If start range is –1, any current selection is removed. The caret is placed at the end of the selection indicated by the greater of the two range values.

Therefore, Charles Petzold's code deselects any currently selected text, in preparation for the EM_REPLACESEL message. However, he assumed that this means the caret will be placed at the end of the edit control after any selection is deselected, since he specified -1 as the ending position, which normally specifies the last character in the edit control. However, this meaning does not get translated into meaning the ending position for the caret. The greater of the two ranges is -1, which is invalid, and is thus, ignored. The caret remains where it is located. Therefore, the inserted text is where you last placed the caret. If you have not moved it, it remains at the end where it should be, and there appears to be no error.

The EM_REPLACESEL Message replaces the current selection in an edit control with the specified text. If there is no current selection, the replacement text is inserted at the current location of the caret. Therefore, the caret must be at the end of the edit control before this is called. The prior SendMessage() did not take care of this.

The proper code is as follows:

Page 747, Chapter 15, DIBHEADS.C - Correction
void Printf (HWND hwnd, TCHAR * szFormat, ...)
{
     TCHAR   szBuffer [1024] ;
     va_list pArgList ;

     va_start (pArgList, szFormat) ;
     wvsprintf (szBuffer, szFormat, pArgList) ;
     va_end (pArgList) ;

     DWORD nChars = (DWORD)SendMessage( hwnd, WM_GETTEXTLENGTH, 0, 0 ); 
     SendMessage( hwnd, EM_SETSEL, (WPARAM)nChars, (LPARAM)nChars ); 
     SendMessage( hwnd, EM_REPLACESEL, FALSE, (LPARAM)szBuffer ); 
     SendMessage( hwnd, EM_SCROLLCARET, 0, 0 );
}

The WM_GETTEXTLENGTH Message determines the length, in characters, of the text associated with the edit control (alternatively, you could call the GetWindowTextLength() function, which in turn sends a WM_GETTEXTLENGTH message). The following SendMessage() command selects one character past the last character of the edit control, so when EM_REPLACESEL is sent, it replaces this character, which is not a character in the edit control.

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

 
Erratum 9: Typo

On page 817, Chapter 15, The DIB Section, in the third bullet item the phrase "during the BitBlt to StretchBlt call" should be "during the BitBlt or StretchBlt call".

Credit: John Kopplin

 
Erratum 10: Source Code Error

On page 810, Chapter 15, The DIB Section, the source code at the top of the page shows:

Page 810, Chapter 15, The DIB Section - Error
bmih->biSize          = sizeof (BITMAPINFOHEADER) ;
bmih->biWidth         = 384 ;
bmih->biHeight        = 256 ;
bmih->biPlanes        = 1 ;
bmih->biBitCount      = 24 ;
bmih->biCompression   = BI_RGB ;
bmih->biSizeImage     = 0 ;
bmih->biXPelsPerMeter = 0 ;
bmih->biYPelsPerMeter = 0 ;
bmih->biClrUsed       = 0 ;
bmih->biClrImportant  = 0 ;

However, since bmih is defined like so, at the bottom of page 809:

BITMAPINFOHEADER bmih ;

It is not a pointer. Therefore, when you initialize the fields of the BITMAPINFOHEADER structure, you cannot use the indirect member access operator (->). You must use the direct member access operator (.) like so:

Page 810, Chapter 15, The DIB Section - Correction
bmih.biSize          = sizeof (BITMAPINFOHEADER) ;
bmih.biWidth         = 384 ;
bmih.biHeight        = 256 ;
bmih.biPlanes        = 1 ;
bmih.biBitCount      = 24 ;
bmih.biCompression   = BI_RGB ;
bmih.biSizeImage     = 0 ;
bmih.biXPelsPerMeter = 0 ;
bmih.biYPelsPerMeter = 0 ;
bmih.biClrUsed       = 0 ;
bmih.biClrImportant  = 0 ;

Credit: Jason Doucette

 
Erratum 11: Source Code Comment Typo

On page 751, Chapter 15, Displaying DIB Information, the comment in the middle of the page states:

"// Display additional BITMAPV4HEADER fields"

This should state:

"// Display additional BITMAPV5HEADER fields"

As, the code is showing data from version 5, not 4.

Credit: Hans Dwarshuis

 
Erratum 12: Source Code Comment Typo

On page 763, Chapter 15, Pixel to Pixel, in the SHOWDIB1.C program, the last comment on the page states:

"// Save the DIB to memory"

This should be:

"// Save the DIB to a disk file"

Credit: Hans Dwarshuis

 
Erratum 13: Text Typo

On page 809, Chapter 15, The DIB Section, in the fifth paragraph, it mentions the variable fColorUse twice. However, you can see at the bottom of the previous page that the correct spelling, to be consistent, is fClrUse.

Credit: Hans Dwarshuis

 
Erratum 14: Source Code Robustness

On page 782, Chapter 15, Sequential Display, in the SEQDISP.C program, the following line of code attempts to compute the size of the bitmap's pixel data:

iBitsSize = bmfh.bfSize - bmfh.bfOffBits ;
However, as you will note in my errata for the DIBHELP.C program in Chapter 16, this method of computing the bitmap's pixed data is OK if the .BMP file is legitimate. However, if it is produced by a number of versions of Adobe Photoshop (6.0 / 6.1 / 7.0 known, for sure), then the method is incorrect. It will compute a value 2 bytes larger than expected, because these .BMP files have 2 extra, useless padding bytes at the end. Please see my errata for the DIBHELP.C program for full information.

Please note that in the SEQDISP.C program this variable being a value of 2 larger than expected will not cause a crash, since it is used only to allocate memory. Allocating 2 bytes more memory than necessary will not cause any problems.

I would like to credit Hans Dwarshuis for pointing out that this line of code was identical to the line of code causing the problem in the DIBHELP.C program, which provoked me to investigate further.

Credit: Jason Doucette and Hans Dwarshuis

 
Erratum 15: Math Error

On page 743, Chapter 15, The Version 4 Header, at the top of the page, it explains what the FXPT2DOT30 type, within the CIEXYZ structure, represents. It stores a fixed-point value, where the 2 most significant bits represent the integer part, and the 30 least significant bits represent the fractional part.

Petzold correctly shows that the 32-bit hexadecimal value 0x40000000 represents the value 1.0. However, the value 0x4800000 represents the value 1.125, not the value 1.5.

It may be easier to understand the values by looking at their binary equivalent. Please note the following values for each bit in the 32-bit 2.30 fixed point representation:

0x80000000 = 10.00 0000 0000 0000 0000 0000 0000 0000 = 2 * 2^30 means 2
0x40000000 = 01.00 0000 0000 0000 0000 0000 0000 0000 = 1 * 2^30 means 1
0x20000000 = 00.10 0000 0000 0000 0000 0000 0000 0000 = 1/2 * 2^30 means 1/2
0x10000000 = 00.01 0000 0000 0000 0000 0000 0000 0000 = 1/4 * 2^30 means 1/4
0x08000000 = 00.00 1000 0000 0000 0000 0000 0000 0000 = 1/8 * 2^30 means 1/8
0x04000000 = 00.00 0100 0000 0000 0000 0000 0000 0000 = 1/16 * 2^30 means 1/16
0x02000000 = 00.00 0010 0000 0000 0000 0000 0000 0000 = 1/32 * 2^30 means 1/32
0x01000000 = 00.00 0001 0000 0000 0000 0000 0000 0000 = 1/64 * 2^30 means 1/64

Thus, 1.5 = 1 + 1/2 = 0x40000000 + 0x20000000 = 0x60000000,
and 1.125 = 1 + 1/8 = 0x40000000 + 0x08000000 = 0x48000000.

If you have the PowerToy Calculator (one of Microsoft's PowerToys), you can select 'Hexadecimal Output' from the View menu, and test these values out yourself:


Credit: Dave Mittleider

    Chapter 16 - The Palette Manager
 
Erratum 1: Windows XP Update

On pages 821 - 995, Chapter 16 deals extensively with 256 (8 bit) color mode. On Windows XP, this mode is not an option under Display Properties. However, there is a way to make this mode available.

Compile (do not run) one of the programs from Chapter 16 that requires a 256 color mode. (If you attempt to run the program as-is, the system will report the error: "This program requires that the video display mode have a 256-color palette.") I would suggest SYSPAL2.C on page 843, as it shows the current system palette, which is useful to see as you run some of the other programs. Find the .exe that was created, right click on it and select Properties. Go to the Compatibility tab. Under 'Display settings', check 'Run in 256 colors' as shown below:

'Run in 256 colors' selection - click to enlarge

Now, run this program, and Windows XP will change into 256 color mode.

The problem with this is that Windows XP will only stay in 256 color mode for the duration of this program. As soon as you close it, Windows XP will switch back into the selected color mode in Display Properties. There is a way to work around this.

While you have a program running in 256 color mode, open Display Properties by right clicking on the Desktop and selecting Properties. Go to the Settings tab. You will see that 'Low (8 bit)' mode is an option under 'Color quality':

'Low (8 bit)' selection shows while in 8-bit mode - click to enlarge

You can now shut down the 256 color mode program, which causes Windows XP to revert back to the old high color display mode, but this leaves the Display Properties window open, and the 'Low (8 bit)' selection remains. However, this is insufficient for our needs, as the 'Apply' button is grayed out:

'Low (8 bit)' selection still shows when back in high color mode - click to enlarge

The program does not allow you to click 'Apply' when no selections have been changed, as resetting the display to what it is already at is a pointless chore. Since the program is unaware that the display mode changed behind its back, this selection remains grayed out. This also means that clicking the 'OK' button will do nothing, as well. Luckily, there is a way to trick the program. You will have to select another mode (just in the selection box, without pressing 'OK' or 'Apply'), then select the 8 bit mode again. Now you will be able to press 'OK' or 'Apply':

'Apply' option now available - click to enlarge

Do so, and this will instruct the operating system to change into the 256 color mode. You will now be in this mode permanently, until you change the Display Properties again. It is evident that Windows XP supports 8 bit color mode, but is attempting to make it obsolete by excluding it from the Color quality list.

Credit: Jason Doucette

 
Erratum 2: Text Typo

On page 829, Chapter 16, Displaying Gray Shades, the first paragraph mentions the GRAYS1 program. This should be GRAYS2.

Credit: Jason Doucette

 
Erratum 3: Source Code Logic Error

On page 828, Chapter 16, Displaying Gray Shades, in the GRAYS2.C program, the WM_PAINT code has a logic error. This is the original code:

Page 828, Chapter 16, GRAYS2.C - Error
       // Draw the fountain of grays

  for (i = 0 ; i < 65 ; i++)
  {
       rect.left   = i * cxClient / 64 ;
       rect.top    = 0 ;
       rect.right  = (i + 1) * cxClient / 64 ;
       rect.bottom = cyClient ;

In the last iteration of the for() loop, i equals 64 (color #65). The following two values will be set:

rect.left   = 64 * cxClient / 64 ; 
rect.right  = 65 * cxClient / 64 ;
rect.left will be set to cxClient, which is the right most edge of the screen. rect.right will be set to some value greater than cxClient, off of the right edge of the screen. Thus, this rectangle is outside of the client area, and is not seen.

The two divisors in this loop should be 65, as they are in GRAYS1.C on page 825, not 64. The corrected code is below:

Page 828, Chapter 16, GRAYS2.C - Correction
       // Draw the fountain of grays

  for (i = 0 ; i < 65 ; i++)
  {
       rect.left   = i * cxClient / 65 ;
       rect.top    = 0 ;
       rect.right  = (i + 1) * cxClient / 65 ;
       rect.bottom = cyClient ;

Please note that this same problem occurs in the GRAYS3.C program at the bottom of page 834. (Thanks to Hans Dwarshuis for reminding me.)

Credit: Jason Doucette

 
Erratum 4: Another Solution

On page 837, Chapter 16, Querying the Palette Support, the text states in the first paragraph:

Page 837, Chapter 16, Querying the Palette Support
It is useful for a Windows program to take a look at this color resolution value and behave accordingly. For example, if the color resolution is 18, it makes no sense for a program to attempt to request 128 shades of gray because only 64 discrete shades of gray are possible. Requesting 128 shades of gray will unnecessarily fill the hardware palette table with redundant entries.

However, you can get more than 64 shades if you increment red, green and blue at separate times, instead of incrementing them all by one at the same time for each new color. The problem with this is that the colors are not pure grays, and the human eye is very good at noticing such flaws. However, this smoother color palette may be desired. This same technique is also possible with other colors, as well.

Credit: Jason Doucette

 
Erratum 5: Source Code Error

In source code CD, the following code in the WM_DISPLAYCHANGE message handler in SYSPAL3.C is wrong:

\Chap16\SysPal3\SYSPAL3.C - Error
     case WM_DISPLAYCHANGE:
          if (!CheckDisplay)
               DestroyWindow (hwnd) ;

It is missing the sole parameter for the CheckDisplay() function. In the text, it is correct:

Page 849, Chapter 16, SYSPAL3.C - Correction
     case WM_DISPLAYCHANGE:
          if (!CheckDisplay (hwnd))
               DestroyWindow (hwnd) ;

Credit: Jason Doucette

 
Erratum 6: Improved Explanation

The following three programs: ALLCOLOR.C on page 862, PIPES.C on page 865, and TUNNEL.C on page 868, all require the PALANIM.C file on page 851. Charles Petzold explains on page 851 that PANANIM.C will contain overhead common to a few palette animation programs in this chapter, but FADER.C on page 860 is the only program (other than the original BOUNCE.C on page 855 that immediately follows PALANIM.C) which states its specific requirement for PALANIM.C.

Credit: Jason Doucette

 
Erratum 7: Source Code Typo

On page 971, Chapter 16, DIBBLE.RC (excerpts), the following code has a typo:

Page 971, Chapter 16, DIBBLE.RC (excerpts) - Error
        POPUP "&Uniform Colors"
        BEGIN
            MENUITEM "&1. 2R x 2G x 2B (8)",        IDM_PAL_RGB222
            MENUITEM "&2. 3R x 3G x 3B (27)",       IDM_PAL_RGB333
            MENUITEM "&3. 4R x 4G x 4B (64)",       IDM_PAL_RGB444
            MENUITEM "&4. 5R x 5G x 5B (125)",      IDM_PAL_RGB555
            MENUITEM "&5. 6R x 6G x 6B (216)",      IDM_PAL_RGB666
            MENUITEM "&6. 7R x 7G x 5B (245)",      IDM_PAL_RGB775
            MENUITEM "&7. 7R x 5B x 7B (245)",      IDM_PAL_RGB757
            MENUITEM "&8. 5R x 7G x 7B (245)",      IDM_PAL_RGB577
            MENUITEM "&9. 8R x 8G x 4B (256)",      IDM_PAL_RGB884
            MENUITEM "&A. 8R x 4G x 8B (256)",      IDM_PAL_RGB848
            MENUITEM "&B. 4R x 8G x 8B (256)",      IDM_PAL_RGB488
        END

In menu item number 7 the text "7R x 5B x 7B" should be "7R x 5G x 7B", as shown below:

Page 971, Chapter 16, DIBBLE.RC (excerpts) - Error
        POPUP "&Uniform Colors"
        BEGIN
            MENUITEM "&1. 2R x 2G x 2B (8)",        IDM_PAL_RGB222
            MENUITEM "&2. 3R x 3G x 3B (27)",       IDM_PAL_RGB333
            MENUITEM "&3. 4R x 4G x 4B (64)",       IDM_PAL_RGB444
            MENUITEM "&4. 5R x 5G x 5B (125)",      IDM_PAL_RGB555
            MENUITEM "&5. 6R x 6G x 6B (216)",      IDM_PAL_RGB666
            MENUITEM "&6. 7R x 7G x 5B (245)",      IDM_PAL_RGB775
            MENUITEM "&7. 7R x 5G x 7B (245)",      IDM_PAL_RGB757
            MENUITEM "&8. 5R x 7G x 7B (245)",      IDM_PAL_RGB577
            MENUITEM "&9. 8R x 8G x 4B (256)",      IDM_PAL_RGB884
            MENUITEM "&A. 8R x 4G x 8B (256)",      IDM_PAL_RGB848
            MENUITEM "&B. 4R x 8G x 8B (256)",      IDM_PAL_RGB488
        END

You can change this manually by opening up DIBBLE.RC in a text editor, making the change, and saving the file. It is easier to do this from the resource editor, however. Simple double click the menu item from the resource editor, and change its caption like so:

Resource Editor - DIBBLE.RC - "DIBBLE" Menu

Credit: Jason Doucette

 
Erratum 8: Text Typo

On page 831, Chapter 16, The Palette Messages, the first sentence of the second to last paragraph refers to QM_QUERYNEWPALETTE. This should be WM_QUERYNEWPALETTE.

Credit: John Kopplin

 
Erratum 9: Source Code Typo

On page 876, Chapter 16, Palettes and Packed DIBs, in the PACKEDIB.C program, the statement in the PackedDibCreatePalette() function:

plp = malloc (sizeof (LOGPALETTE) *
                    (iNumColors - 1) * sizeof (PALETTEENTRY)) ;

should be:

plp = malloc (sizeof (LOGPALETTE) +
                    (iNumColors - 1) * sizeof (PALETTEENTRY)) ;

Credit: John Kopplin

 
Erratum 10: Source Code Comment Error

On page 911, Chapter 16, Palettes and DIB Sections, in the SHOWDIB8.C program, the following source code comment in the WM_COMMAND handling code:

// If there's an existing packed DIB, free the memory

should be:

// If there's an existing bitmap object, free the memory

Credit: John Kopplin

 
Erratum 11: Source Code Comment Error

On pages 925 and 926, Chapter 16, The Information Functions, in the DIBHELP.C (first part) program, the comments for DibGetColor() and DibSetColor() are reversed.

Credit: John Kopplin

 
Erratum 12: Major Bug Causing Invalid Page Fault

On page 928, Chapter 16, Reading and Writing Pixels, in the DIBHELP.C (second part) program, there is an error in the source code on this line of the DibGetPixel() function:

case 24: return 0x00FFFFFF & * (DWORD *) pPixel;

The program causes the DIBBLE.C program on page 949 to crash due to an invalid page fault on certain 24-bit color bitmaps, particularly when a flip command is followed by another flip command.

The crash occurs at the end of the final row when pPixel is only guaranteed to point to an RGBTRIPLE's worth (3 bytes) of memory in our process's address space. But this line of code casts pPixel to a DWORD and then attempts to dereference 4 bytes which is not proper. The program does not crash on all 24-bit color DIBs because, depending upon the bitmap's dimensions, the 4th byte may or may not be within our process's address space. Recall that each row in the DIB is expanded to be a multiple of 4 bytes, if it is not already. In the cases where expansion is needed, we have some extra junk bytes in our process's address space and therefore the program does not crash. In cases where there is no expansion (the 3 bytes we wish to address are the last 3 bytes of the DIB), the next byte may still be within our process's address space simply because the memory that follows it is used by some other variable / structure owned by our process, and thus the program will not crash. This appears to be the case for the first flip command.

To fix this program, the program should only read 3 bytes from pPixel rather than 4. The following code type casts pPixel into a pointer to RGBTRIPLE, so that we only access the 3 bytes of memory that we have to. It then constructs a DWORD value from the RGBTRIPLE structure, to be returned:

case 24:
     {
          RGBTRIPLE rgbcolor = * (RGBTRIPLE *) pPixel;
          DWORD dwordcolor = (rgbcolor.rgbtRed   << 16)
                           + (rgbcolor.rgbtGreen << 8 )
                           +  rgbcolor.rgbtBlue;
          return dwordcolor ;
     }

This is not particularly efficient, but it gets the job done, and shows a possible method to avoid accessing memory that we should not access.

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

 
Erratum 13: Source Code Omission

On page 947, Chapter 16, The DIBHELP Header File and Macros, in the DIBHELP.H source file, there is a function declared as follows:

HDIB DibCreateFromDdb (HBITMAP hBitmap) ;

This apparently was never implemented in DIBHELP.C. It would have appeared after the DibCopyToDib() function on page 945.

Credit: John Kopplin

 
Erratum 14: Source Code Logic Error

On pages 956 and 957, Chapter 16, The DIBBLE Program, in the DIBBLE.C program there is a logic error that causes the program to over scroll by 1 pixel both horizontally and vertically. The following statement on page 956:

si.nMax = DibHeight (hdib);

should be:

si.nMax = DibHeight (hdib) - 1;

The following statement on page 957:

si.nMax = DibWidth (hdib);

should be:

si.nMax = DibWidth (hdib) - 1;

This can be observed when you load a bitmap that is larger than the program's client area size (shrink the client area size yourself if you do not have a bitmap large enough), and scroll both scroll bars to their maximum amounts. A zooming program, such as ZoomView, will show a white line (the window's background color) a pixel thick on the bottom and right edges of the image.

Credit: John Kopplin

 
Erratum 15: Source Code Bug - Memory Leak

On page 967, Chapter 16, The DIBBLE Program, in the DIBBLE.C source file, in the middle of the page there is a memory leak caused by the statement:

hBitmap = DibCopyToDdb (hdib, hwnd, hPalette);

Before you assign a new value to hBitmap, its prior value (recall it is a static variable) needs to be freed using the DeleteObject() function, like so:

DeleteObject (hBitmap) ;

You can see an instance of the correct method on page 964 in response to the IDM_EDIT_ROTATE and IDM_EDIT_FLIP messages.

On page 968, Chapter 16, The DIBBLE Program, in the DIBBLE.C source file, about two-thirds of the way down the page, a similar memory leak occurs in an identical line of code.

Credit: John Kopplin

 
Erratum 16: Source Code Robustness - Potential Buffer Overflow

On page 943, Chapter 16, Creating and Converting, in the DIBHELP.C (third part) source file, there is an assumption made in the DibFileLoad() function that is not always correct. It appears in the following line of code, which determines the size of a bitmap's pixel data section:
dwBitsSize = bmfh.bfSize - bmfh.bfOffBits ;
bmfh.bfSize is the size of the BMP file in bytes. bmfh.bfOffBits is the offset to the bitmap pixel data, from the start of the file, in bytes. Since the bitmap pixel data is the last structure in a BMP file, the line of code calculates the size of this structure with the assumption that all of the information starting at the bmfh.bfOffBits offset is bitmap pixel data. After all, there is no structure that follows it, so this seems like a reasonable assumption - and it is.

To further explain, I will show a quick review of how BMP files are stored. They are composed of three or four structures in the following sequence:
  1. BITMAPFILEHEADER structure
  2. BITMAPINFOHEADER structure (or BITMAPCOREHEADER structure for older OS/2-compatible BMP files)
  3. The color table / palette information. (For indexed color files only. 24-bit and 32-bit color BMP files are not indexed; their bitmap pixel data stores the actual RGB color values, not an index into a color table.)
  4. The bitmap pixel data
For example, in a 24-bit color BMP of size 512 x 512, the size of the file is 786,486 bytes. The file itself consists of the following:
  1. The BITMAPFILEHEADER is 14 bytes.
  2. The BITMAPINFOHEADER is 40 bytes (if this were an OS/2-compatible BMP, this structure would be the BITMAPCOREHEADER structure, which is 12 bytes).
  3. There is no color table, as the color data is stored in the bitmap pixel data itself.
  4. The bitmap pixel data needs to store 24-bits (3 bytes) per pixel for 512 rows and 512 columns of pixels. Since the size of each row (512 * 3 = 1,536) is divisible by 4, there is no extra padding on each row. There are 512 rows of this size, making a total of 786,432 bytes (1,536 * 512 = 786,432).
14 + 40 + 786,432 = 786,486 bytes; the size of the file.

Charles Petzold's code makes the assumption that the size of the last field equals the size of the file minus the size of the fields that precede it (i.e. the size of the file minus the offset to where this field starts). This is a valid assumption, except in the case where a BMP file is created that does not adhere to the BMP file format definition. Adobe Photoshop 6.0 / 6.1 / 7.0 is known to create BMP files that are 2 bytes larger than expected. These extra bytes are packed on the end of the file, making Charles Petzold's code think the bitmap pixel data size is 2 bytes larger than expected. When this size is passed to the ReadFile() function immediately following the above line of code:
bSuccess = ReadFile (hFile, ((PDIBSTRUCT) hDib)->pBits, 
                     dwBitsSize, &dwBytesRead, NULL);
...it attempts to read 2 bytes more than what it knows can fit into allocated memory, and fails. It returns a value of 0 into dwBytesRead, indicating that it did not read a single byte from the file, and returns an extended error code (which you must use GetLastError() to retrieve) of ERROR_NOACCESS (998), which means, "Invalid access to memory location".

To correct this problem we will use the BITMAPINFOHEADER information (or the BITMAPCOREHEADER information for an OS/2 bitmap) to calculate the bitmap's pixel data size. The DibFileLoad() function has a variable called pbmi which points to a BITMAPINFO structure (when loading an OS/2 bitmap, it actually points to a BITMAPCOREINFO structure). The first field of this structure is a BITMAPINFOHEADER structure (or a BITMAPCOREHEADER structure for an OS/2 bitmap). The data pbmi points to is used in a call to DibCreateFromInfo(), and then it is deallocated immediately. Before it is freed, use the following code to extract the size of the bitmap pixel data (I use C++ declarations here):
DWORD RowLength;
if (dwInfoSize == 40)
{
	// handle Windows bitmap
	BITMAPINFOHEADER &bmih = pbmi->bmiHeader;
	RowLength = ((bmih.biWidth * bmih.biBitCount + 31) & ~31) >> 3 ;
	dwBitsSize = RowLength * bmih.biHeight;
}
else // dwInfoSize == 12
{
	// handle OS/2 bitmap
	BITMAPCOREHEADER &bmch = ((BITMAPCOREINFO*)pbmi)->bmciHeader;
	RowLength = ((bmch.bcWidth * bmch.bcBitCount + 31) & ~31) >> 3 ;
	dwBitsSize = RowLength * bmch.bcHeight;
}
Note that we have two cases: one for Windows bitmaps, and one for OS/2 bitmaps. The code in each case is conceptually the same, it just refers to two different structures that pbmi may be pointing to. Therefore, I will explain both pieces of code at the same time. The first step is to create a reference to the BITMAPINFOHEADER (or BITMAPCOREHEADER for an OS/2 bitmap) structure, so we can access its data more easily than continually dereferencing pbmi. The RowLength variable computes the length of each row of bitmap pixel data, using Charles Petzold's formula from the top of page 729, Chapter 15, The DIB Pixel Bits. The total bitmap pixel data size is computed by multiplying the size of each row by the number of rows, and stored into dwBitsSize. Now, when you attempt to load a BMP file saved from Adobe Photoshop 6.0 / 6.1 / 7.0, the size will be computed properly, and the program will not attempt to read 2 more bytes than it should.

If anyone has any information regarding why Adobe Photoshop 6.0 / 6.1 / 7.0 saves BMP files with an extra two junk bytes at the end, or has any information on other software packages that save BMPs in an improper way, please contact me at the email address located at the bottom of this page.

Credit: Jason Doucette

 
Erratum 17: Better Code

On page 916, Chapter 16, A LIBRARY FOR DIBS, the text explains that you may want to create your own handle to a bunch of information that the interface should know nothing about. Only the implementation will know what this handle actually is. On this page, Petzold creates an HDIB handle, defined like so:
typedef void * HDIB ;
All the interface knows is that it is a pointer. It doesn't know what it points to. It actually points to a structure. The implementation knows this, and will type cast this handle to a pointer to that type, and then proceed to access its fields.

What's wrong with all of this? Nothing, except all handles that are declared in this manner are all pointers to void. Thus, they all have the same type. This means that you could pass a handle to a function expecting another handle, and the compiler will not complain. The solution to this mess is to do what the Windows programmers do. You can declare a handle using a clever definition DECLARE_HANDLE like so:
DECLARE_HANDLE (HDIB) ;
DECLARE_HANDLE is defined in winnt.h as follows:
#ifdef STRICT
typedef void *HANDLE;
#define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name
#else
typedef PVOID HANDLE;
#define DECLARE_HANDLE(name) typedef HANDLE name
#endif
First of all, you'll notice there are two different possible declarations of DECLARE_HANDLE depending on whether STRICT is defined or not. If it is not defined, you can see that using DECLARE_HANDLE does nothing more than define the type as a pointer to void:
typedef PVOID HANDLE;
#define DECLARE_HANDLE(name) typedef HANDLE name
This is the same as what we originally had. When STRICT is defined, however, things are better (and look more complicated):
typedef void *HANDLE;
#define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name
What exactly is the STRICT definition? Allow me to quote MSDN: "When you define the STRICT symbol, you enable features that require more care in declaring and using types. This helps you write more portable code. This extra care will also reduce your debugging time. Enabling STRICT redefines certain data types so that the compiler does not permit assignment from one type to another without an explicit cast. This is especially helpful with Windows code. Errors in passing data types are reported at compile time instead of causing fatal errors at run time."

It is highly recommended that you define STRICT. Getting back to DECLARE_HANDLE, let's break its definition down. First, it declares a structure with a unique name, that has a single field:
struct name##__ { int unused; };
The structure's name will be the name you pass to DECLARE_HANDLE with an additional two underscores padded on the end. Then, the handle that you wish to declare is declared as a pointer to this structure:
typedef struct name##__ *name
Therefore, each handle you declare with DECLARE_HANDLE points to a unique type. Thus, you cannot accidentally pass a handle to a function desiring a handle to something else.

(You will notice that all of this only pertains to C++. It is not an issue in C, since it does not have type checking. It allows just about anything to be type cast to anything else, whether you want to or not.)

Credit: Jason Doucette

 
Erratum 18: Code Logic Incorrect

On page 936, Chapter 16, Creating and Converting, in the DIBHELP.C program (third part), the function DibCreate() has a logic error.

As explained on page 946, in the second paragraph:

"DibCreate is probably a more likely function than DibCreateFromInfo to be called from application programs. The first three arguments provide the pixel width and height and the number of bits per pixel. The last argument can be set to 0 for a color table of default size or to a nonzero value to indicate a smaller color table than would otherwise be implied by the bit count."

Thus, there are two choices for the cColors parameter. The default is 0, in which the function will compute the proper color table size. Any non-zero value states you are specifying the color table size.

Move on to the code at the top of page 936:
     if (cColors != 0)
          cEntries = cColors ;
     else if (cBits <= 8)
          cEntries = 1 << cBits ;
You will notice that the first section determines a value for cEntries, which is the color table size (the number of entries in the color table). The first part of the if-statement correctly takes the non-zero value for cColors, and sets the color table size to the specification passed in:
     if (cColors != 0)
          cEntries = cColors ;
     ...
The else clause implies what to do when cColors is zero. In this case it is supposed to set the color table size to what the bit count implies. But, all that exists in the else clause is:
     if (cBits <= 8)
          cEntries = 1 << cBits ;
It checks only those bitmaps that have 8-bits per color or less! For 16-bit (or larger) color bitmaps, cEntries is not even set!

The solution is simple. For 16-bit (or larger) color bitmaps, no color table exists, thus cEntries should be set to 0. Fix the entire if-else-statement as follows:
     if (cColors != 0)
     {
          // cColors is non-zero, meaning cColors IS the color table size.

          cEntries = cColors ;
     }
     else
     {
          // cColors is zero, meaning the color table size should
          // be set to the size implied by the color bit count.

          if (cBits <= 8)
              cEntries = 1 << cBits ;
          else
              cEntries = 0 ;
     }
Please note: We are not done. Read the next errata for further information about what is wrong with the following line of code that uses the cEntries value.

Credit: Jason Doucette

 
Erratum 19: Code Logic Incorrect

On page 936, Chapter 16, Creating and Converting, in the DIBHELP.C program (third part), the function DibCreate() has a logic error.

After the if-statement that sets the cEntries variable (please see previous errata for information on how this is computed incorrectly), it is used to determine the size of a memory allocation in the following line of code:
     dwInfoSize = sizeof (BITMAPINFOHEADER) + (cEntries - 1) * sizeof (RGBQUAD);
The bug is due to the fact that when cEntries is 0, we store a size into dwInfoSize that is 4 bytes smaller than the BITMAPINFOHEADER structure. Since the function continues on to fill this structure, we'll overrun the allocated memory.

Where did this bug come from? Likely, it is copied from code on page 727, in Chapter 15, where Petzold states:

"If you need to allocate a structure of PBITMAPCOREINFO for an 8-bit DIB, you can do it like so:
pbmci = malloc (sizeof (BITMAPCOREINFO) + 255 * sizeof (RGBTRIPLE)) ;
"

But, this code is correct, as he wants a color table sized 256, and the BITMAPCOREINFO structure already has one RGBTRIPLE structure, so we need 255 more. That is, one less than the total number we need. But, BITMAPCOREINFO is for OS/2 style bitmaps. If you continue reading Chapter 15 to page 731, you'll see that, for Windows style bitmaps, it is the BITMAPINFO structure we wish to deal with. And it is the one that already contains one RGBQUAD structure, not the BITMAPINFOHEADER structure. Thus the code on page 936 merely uses the wrong structure.

So, this bug can be fixed by changing the code like so:
     dwInfoSize = sizeof (BITMAPINFO) + (cEntries - 1) * sizeof (RGBQUAD);
Some background information, for further reading:

The BITMAPINFO structure is defined like so:
typedef struct tagBITMAPINFO 
{ 
  BITMAPINFOHEADER bmiHeader; 
  RGBQUAD          bmiColors[1]; 
} BITMAPINFO;
You will note that the first field is a BITMAPINFOHEADER structure.

For regular 16-bit, 24-bit, and 32-bit DIBs, there is no color table required, and when you call the CreateDIBSection() function, although it wants a pointer to BITMAPINFO for its second parameter, it is content with getting a pointer to BITMAPINFOHEADER. This is the first field of BITMAPINFO, so it is basically a BITMAPINFO without the color table.

So, because DibCreate() on page 935 is creating a BITMAPINFO to be passed to the DibCreateFromInfo() function, which passes it directly into the CreateDIBSection() function, it is OK if we create our BITMAPINFO with just the first field.

Thus, both of the following pieces of code accomplish the same thing, and fixes the bug:
     dwInfoSize = sizeof (BITMAPINFO)       + (cEntries - 1) * sizeof (RGBQUAD);
     dwInfoSize = sizeof (BITMAPINFOHEADER) +  cEntries      * sizeof (RGBQUAD);
Thus, a pointer to just a BITMAPINFOHEADERHEADER is the same thing as a pointer to a BITMAPINFO structure without the bmiColors field set. And, either is good enough to be passed to our DibCreateFromInfo() function, and to the CreateDIBSection() function.

Credit: Jason Doucette

    Chapter 17 - Text and Fonts
 
Erratum 1: Source Code Bug

On page 1012, Chapter 17, The PICKFONT Program, in the PICKFONT.C program, the first statement on the page is a SendMessage() call. This is run after the user selects either menu selection from the Device menu. It is supposed to send a fake message that indicates the 'OK' button was pressed, which causes the handler of the 'OK' button to be run, which starts at the bottom of page 1016. The problem is that the message is not being sent to the proper window. It should be sent to the button's owner, the dialog box, not to the main window, which does not own the 'OK' button.

You can fix the problem by changing the first parameter in the SendMessage() call from hwnd to hdlg, like so:

SendMessage (hdlg, WM_COMMAND, IDOK, 0) ;

Please note that a similar SendMessage() call exists in the middle of page 1013, but this one properly sends the message to the proper window.

Credit: Jason Doucette

 
Erratum 2: Typo

On page 1026, Chapter 17, The Logical Font Structure, the sentence following the one containing this source code:

LOGFONT lf ;

states:

"When finish, you call CreateFontIndirect with a pointer to the structure:"

It should state:

"When finished, you call CreateFontIndirect with a pointer to the structure:"

Credit: Kim Gräsman

 
Erratum 3: Typo

On page 1030, Chapter 17, The Logical Font Structure, the table on the top of the page mentions the FW_DONTCARE identifier. However, this identifier is indented for the lfWeight field of the LOGFONT structure, as explained on page 1028. The identifier should be FF_DONTCARE for the lfPitchAndFamily field.

Credit: Kim Gräsman

 
Erratum 4: Typo

On page 1058, Chapter 17, Simple Text Formatting, the following source code line is in error:

TCHAR * szText [] = TEXT ("Hello, how are you?") ;

The asterisk (*) should be removed. Once this is done, you will be declaring the variable szText as an array of TCHAR. It will be initialized and sized to the length of the following string, including the ending NULL character.

Removing the square brackets [] would also be accepted. In this case, you are declaring the variables szText as a pointer to TCHAR. It will be initialized to point to the string literal that follows it.

Including both the asterisk (*) and the square brackets [] indicates that you with to declare szText as an array of pointer to TCHAR.

Credit: Kim Gräsman

    Chapter 18 - Metafiles
 
Erratum 1: Example Programs Requirements

Some of the example programs in Chapter 18 require files that are created by earlier programs within the same chapter, as explained in the readme.txt file that accompanies the text book:

\readme.txt
Some of the programs in Chapter 18 create files that are used by
other programs in the chapter. Thus, these programs must be run in
a specific order.

Credit: Charles Petzold

Section
III

Advanced
Topics
    Chapter 19 - The Multiple-Document Interface
 
Erratum 1: Usability Warning

Take heed to the following note from MSDN's page on Multiple Document Interface:

"MDI is an application-oriented model. Many new and intermediate users find it difficult to learn to use MDI applications. Therefore, many applications are switching to a document-oriented model. Therefore, you may want to consider other models for your user interface. However, you can use MDI for applications which do not easily fit into an existing model until a more suitable model is introduced."

Credit: Microsoft Developer Network

 
Erratum 2: Information Source

On page 1195, Chapter 19, The Child Document Windows, in the last paragraph of the page, it states which messages DefMDIChildProc should handle, even if your child window procedure does something with them, or not. This information was acquired from the MSDN page on the DefMDIChildProc Function, which also explains DefMDIChildProc's response to handling each message.

Credit: Jason Doucette

 
Erratum 3: Program Bug

On page 1187, Chapter 19, A SAMPLE MDI IMPLEMENTATION, in the MDIDEMO.C program, there is a program bug that can cause a division by zero error, which will crash the program. Take a look at the following four assignment statements at the top of the page:
xLeft   = rand () % pRectData->cxClient ;
xRight  = rand () % pRectData->cxClient ;
yTop    = rand () % pRectData->cyClient ;
yBottom = rand () % pRectData->cyClient ;
When the window size is zero, this causes a division by zero (a modulus operation is actually a division operation in which the remainder is returned instead of the quotient).

There is also a less subtle bug that is caused by this code. Since the boundaries of a rectangle to be drawn are computed using the window's current size in the modulus operation, the resultant random coordinates will never touch the rightmost column or the bottommost row. This is a result of the fact that the Rectangle() function "excludes the bottom and right edges", as does all of the Windows GDI functions.

To resolve both of these issues at the same time, change the code to the following:
xLeft   = rand () % (pRectData->cxClient + 1) ;
xRight  = rand () % (pRectData->cxClient + 1) ;
yTop    = rand () % (pRectData->cyClient + 1) ;
yBottom = rand () % (pRectData->cyClient + 1) ;
This ensures that the random coordinates that are chosen for the rectangle include the rightmost column and bottommost row, and also avoids any possibility of passing a zero value into the modulus operation.

Credit: Jason Doucette

    Chapter 20 - Multitasking and Multithreading
 
Erratum 1: Synchronization Errors

On pages 1205 - 1207, Chapter 20, Random Rectangles Revisited, the RNDRCTMT.C program has a problem with synchronization. Occasionally, when the user closes the program, the main window is destroyed, yet the thread drawing the rectangles is still running. This causes the following line of code:
hdc = GetDC (hwnd);
to store a device context for the entire screen (as if GetDC() were passed NULL) into hdc. Through experimentation on my Windows XP system, this may cause it to draw the border of the rectangle before the thread is terminated (if it gets this far, it does not get a chance to fill it with the current brush - perhaps because the brush has been deleted by this point). This leaves the remnants of the rectangle's border on whatever window was behind RNDRCTMT. The border is usually near the upper-left corner of the entire screen, as the coordinates passed to the Rectangle() function were in relation to the entire screen, not the destroyed window's client area. However, the remnants are usually deleted in most cases, as the destruction of the main window of RNDRCTMT causes WM_PAINT messages to be sent to all windows beneath it that are now visible. A lot of programs merely repaint their entire window, erasing the artifact left over. The best chance to see this artifact is to run the program over the desktop window only (i.e. let RNDRCTMT be the only window open; minimize all others). Ensure that the desktop has repainted itself fully (by waiting for all of the desktop icons to appear) before closing down RNDRCTMT. This precaution is necessary as the program slows down repainting of the desktop after you have minimized the IDE window of your compiler. This is to ensure that Windows will not be in the middle of processing a WM_PAINT message for the desktop window when you close the application, as this repainting may paint over the artifact left over upon program termination. Once this is ensured, close RNDRCTMT. It may take a few tries, but you will eventually see the remnants of an unfinished rectangle - just its border remains. Since the WM_PAINT message to the desktop only repaints the area that was covered by RNDRCTMT, this rectangle border remains in view.

There is a second problem with synchronization. This one is easier to detect. Simply shrink the window down so that its client area size is zero. The section of the code that follows may cause an error:
          if (cxClient != 0 || cyClient != 0)
          {
               xLeft   = rand () % cxClient ;
               xRight  = rand () % cxClient ;
               yTop    = rand () % cyClient ;
               yBottom = rand () % cyClient ;
Even though cxClient and cyClient are checked to ensure they are not zero, because the application is not synchronized properly, these values can change before they are used in the following four assignment statements. If they become zero during the execution of these assignment statements, the if statement will be too late to catch the problem, and a division by zero will occur (the modulus operator (%) is a division). This will crash the program.

There is a third problem in RNDRCTMT.C. The fact that the thread function Thread() never exits is improper. You are supposed to end a thread started by _beginthread() by calling _endthread explicitly or by letting the thread function return (which automatically calls the _endthread() function). RNDRCTMT does neither.

I should note that Charles Petzold intended this program to be a simple introduction to how multithreaded applications work. It is meant to be a sample program to show only a few of the necessary components of a multithreaded application. He later speaks of such problems that exist in these sample applications on page 1226, Chapter 20, Any Problems? The chapter then goes on to explain how to resolve such problems. Therefore, there is not much need to attempt to fix this problem until the remainder of the chapter has been reviewed. It was intentional that the specifics of multithreading was taught incrementally throughout the chapter, which results in these first sample programs being incomplete.

Credit: Jason Doucette

 
Erratum 2: APIENTRY vs WINAPI Usage Inconsistency

The MULTI1.C program (on pages 1209 - 1215, Chapter 20, The Programming Contest Problem), the MULTI2.C program (on pages 1216 - 1224, Chapter 20, The Multithreading Solution), and the BIGJOB1.C program (on pages 1230 - 1233, Chapter 20, The BIGJOB1 Program) all use the APIENTRY calling convention several times instead of the standard WINAPI calling convention used throughout the rest of the book.

However, APIENTRY is defined as WINAPI in windef.h like so:
#define APIENTRY    WINAPI
It does not matter which one you choose, as they are the same calling convention. They are both directly or indirectly defined as __stdcall. It is the calling convention used to call Win32 API functions. Please refer to the MSDN page for __stdcall to view its implementation.

Credit: Jason Doucette

 
Erratum 3: Correction

On page 1230, Chapter 20, The BIGJOB1 Program, the second sentence within the first paragraph mentions incrementing an integer. In the actual program, BIGJOB1.C, the variable that is incremented, A, is of type double, which is a floating point type.

Credit: Jason Doucette

 
Erratum 4: Improved Explanation

On page 1241, Chapter 20, THREAD LOCAL STORAGE, Charles Petzold explains a possibility of how Thread Local Storage may be implemented with the TlsAlloc(), TlsSetValue(), TlsGetValue(), and TlsFree() functions. His explanation is certainly plausible, and this 'under the hood' detailing of the four Thread Local Storage functions helps a new user understand their purpose for existing, and they will find their usage easier as a result.

However, the explanation is not correct. The TlsAlloc() function returns a TLS index. While this function's MSDN page does not explain exactly what the TLS index is (Charles thought it may be a pointer to allocated memory), the TlsSetValue() and TlsGetValue() MSDN pages explain a little more. They both state that they were implemented with speed as the primary goal. In this regard, the only parameter validation that is performed on each is to ensure that the TLS index passed in is in the range 0 through (TLS_MINIMUM_AVAILABLE – 1). Thus, the TLS indices are not pointers.

The Thread Local Storage MSDN page explains exactly how the system implements Thread Local Storage. Please refer to it for further explanation.

Credit: Jason Doucette

 
Erratum 5: Correction

On page 1234, Chapter 20, The BIGJOB1 Program, the last paragraph mentions a KillThread function. However, there is no such function. Charles Petzold must have meant TerminateThread.

Credit: Kim Gräsman

    Chapter 21 - Dynamic-Link Libraries
 
Erratum 1: Source Code Typo

On page 1249, Chapter 21, The Test Program, the szAppName[] array is initialized with the value "StrProg" which is obviously a cut and paste error indicating that Charles Petzold wrote the StrProg program on page 1258 first, and then modified it to become the EdrTest program on page 1249.

Credit: John Kopplin

 
Erratum 2: Source Code Error

On page 1253, Chapter 21, Shared Memory in DLLs, the following definition of the call-back function within the calling program has an error:

EXPORT BOOL CALLBACK GetStrCallBack (PSTR pString, PVOID pParam)

The EXPORT storage-class attribute should not exist, as this function is within the calling program, not the DLL. Therefore, its definition should be as follows:

BOOL CALLBACK GetStrCallBack (PSTR pString, PVOID pParam)

You will note its actual definition within the STRPROG.C calling program on page 1260 is proper, as follows:

BOOL CALLBACK GetStrCallBack (PTSTR pString, CBPARAM * pcbp)

Credit: Ruben Soto

 
Erratum 3: Logic Error

On page 1257, Chapter 21, Shared Memory in DLLs, in the STRLIB.C file, the GetStringsA function has a logic error. The return value from the pfnGetStrCallBack call is checked, and can cause a return before pAnsiStr is freed. Therefore, the line of code that frees pAnsiStr should be placed before the if-statement that checks the value of bReturn, but after it is used in the pfnGetStrCallBack call.

Credit: Gary Kato

 
Erratum 4: Typo

On page 1248, Chapter 21, A Simple DLL, the third line states, "EDRLIB.H also includes a function named DllMain, which takes the place of WinMain in a DLL." The text should state EDRLIB.C

Credit: Walt Mendenhall

    Chapter 22 - Sound and Music
 
Erratum 1: Source Code Logic Error

On page 1294, Chapter 22, Generating Sine Waves in Software, in the SINEWAVE.C program, there is a bug since the call to waveOutUnprepareHeader() follows the call to waveOutClose(). After the call to waveOutClose() the hWaveOut is no longer valid yet is being used in the call to waveOutUnprepareHeader().

Credit: John Kopplin

 
Erratum 2: Source Code Logic Error

On page 1292, Chapter 22, Generating Sine Waves in Software, in the SINEWAVE.C program, there are bugs in the handling of the IDC_ONOFF message, where it allocates memory. The following code contains the bugs:

pWaveHdr1 = malloc (sizeof (WAVEHDR)) ;
pWaveHdr2 = malloc (sizeof (WAVEHDR)) ;
pBuffer1  = malloc (OUT_BUFFER_SIZE) ;
pBuffer2  = malloc (OUT_BUFFER_SIZE) ;

if (!pWaveHdr1 || !pWaveHdr2 || !pBuffer1 || !pBuffer2)
{
     if (!pWaveHdr1) free (pWaveHdr1) ;
     if (!pWaveHdr2) free (pWaveHdr2) ;
     if (!pBuffer1)  free (pBuffer1) ;
     if (!pBuffer2)  free (pBuffer2) ;
You will notice that when malloc() fails to allocate the desired memory block, it returns NULL. NULL is defined as 0. If it succeeds, it returns an address which is never equivalent to 0. C / C++ defines false as 0, and any non-zero integer as true. Therefore using an address as the expression in an if statement will result in the statement that follows it to be executed if the address actually points to something, like so:

pMemory = malloc (bytestoallocate) ;

if (pMemory)
{
     malloc was successful.
}
else
{
     malloc failed.
}
You will note, in Charles Petzold's code, that he places a Logical-NOT operator (!) before the address, which reverses the logic, like so:

pMemory = malloc (bytestoallocate) ;

if (!pMemory)
{
     malloc failed.
}
else
{
     malloc was successful.
}
Therefore, his code actually attempts to deallocate only the memory that failed to allocate. We wish to deallocate the memory that was successfully allocated. The correct code is as follows; fixed simply by removing the Logical-NOT operators (!):

pWaveHdr1 = malloc (sizeof (WAVEHDR)) ;
pWaveHdr2 = malloc (sizeof (WAVEHDR)) ;
pBuffer1  = malloc (OUT_BUFFER_SIZE) ;
pBuffer2  = malloc (OUT_BUFFER_SIZE) ;

if (!pWaveHdr1 || !pWaveHdr2 || !pBuffer1 || !pBuffer2)
{
     if (pWaveHdr1) free (pWaveHdr1) ;
     if (pWaveHdr2) free (pWaveHdr2) ;
     if (pBuffer1)  free (pBuffer1) ;
     if (pBuffer2)  free (pBuffer2) ;
This is a nice example to demonstrate why this is poorly written code, and what should be done to improve it. It is common for C / C++ programmers to write code as tightly as possible, and if they can avoid an explicit check of an address to NULL, they will do so. What does this accomplish? It does not make the final code faster, and even if it did, it would be so negligible that it would not be worth it for this sole reason. It makes the code much harder to read. Even once you are used to such code, it can come back to bite you in the leg because it is hard to read. The evidence above shows that even highly experienced C programmers can make this mistake. In the end, such code serves no other purpose than to save a few keystrokes which does not make up for all of the potential problems that come along with it. I highly recommend the following code, instead:

pWaveHdr1 = malloc (sizeof (WAVEHDR)) ;
pWaveHdr2 = malloc (sizeof (WAVEHDR)) ;
pBuffer1  = malloc (OUT_BUFFER_SIZE) ;
pBuffer2  = malloc (OUT_BUFFER_SIZE) ;

if (!pWaveHdr1 || !pWaveHdr2 || !pBuffer1 || !pBuffer2)
{
     if (pWaveHdr1 != NULL) free (pWaveHdr1) ;
     if (pWaveHdr2 != NULL) free (pWaveHdr2) ;
     if (pBuffer1 != NULL)  free (pBuffer1) ;
     if (pBuffer2 != NULL)  free (pBuffer2) ;

Credit: Ruben Soto

 
Erratum 3: Typo

On page 1404, Chapter 22, RIFF File I/O, the second sentence on the page states:

"Following the mmioWrite call to write the string szSoftware, a call to mmioAscent using mmckinfo[2] fills in the chunk size field for this chunk."

However, it should read:

"Following the mmioWrite call to write the string szSoftware, a call to mmioAscend using mmckinfo[2] fills in the chunk size field for this chunk."

Credit: Kim Gräsman

 
Erratum 4: Improper Subclass Implementation

On page 1336, Chapter 22, in the WAKEUP.C program the program uses 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 this program has nothing to clean up, it is technically ok. However, if the subclass allocated memory or did something else which had to be cleaned up before termination, then it 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 385, Chapter 9, in the COLORS1.C program, and on page 411, Chapter 9, in the HEAD.C program.

Credit: Jason Doucette

    Chapter 23 - A Taste of the Internet
 
Erratum 1: Source Code Error

On page 1429, Chapter 23, The Update Demo, in the UPDDEMO.C program the InternetOpen() call should have a last argument of 0. Charles Petzold has not implemented a call-back function so he cannot use asynchronous I/O.

To correct this error, change the following lines of code from this:

      hIntSession = InternetOpen (szAppName, INTERNET_OPEN_TYPE_PRECONFIG,
                                 NULL, NULL, INTERNET_FLAG_ASYNC) ;

To these following lines:

     hIntSession = InternetOpen (szAppName, INTERNET_OPEN_TYPE_PRECONFIG,
                                 NULL, NULL, 0) ;

Credit: Kia Vang

 
Erratum 2: FTP Site Down

On page 1423, Chapter 23, The Update Demo, the UPDDEMO.C program attempts to access files in the directory /ProgWin/UpdDemo on the FTP server on cpetzold.com:

     // Information for FTP download

#define FTPSERVER TEXT ("ftp.cpetzold.com")
#define DIRECTORY TEXT ("cpetzold.com/ProgWin/UpdDemo")
#define TEMPLATE  TEXT ("UD??????.TXT")
As mentioned in the About Author section above, the domain cpetzold.com is no longer up. The author's new domain is www.charlespetzold.com. There is an FTP server set up on charlespetzold.com, ftp.charlespetzold.com, but there is no /ProgWin/UpdDemo directory. The only directories that exist are those for previous versions of Programming Windows. Without an appropriate FTP server with the appropriate files, this program cannot be tested.

If any one has an FTP server that they can lend for use with this errata page, please contact me, and let me know. I can then include appropriate information here, so readers can change their source code to test this program.

Credit: Paul Hornby

 
Erratum 3: Typo

On page 1418, Chapter 23, The NETTIME Program, the first sentence in the paragraph starting just below the middle of the page states:

"NETTIME next calls WSAAsynchSelect, which is another Windows-specific sockets function."

However, the function WSAAsyncSelect, is misspelled. It should not have an 'h':

"NETTIME next calls WSAAsyncSelect, which is another Windows-specific sockets function."

Credit: Kim Gräsman

 
Erratum 4: Typo

On page 1422, Chapter 23, Overview of the FTP API, the last sentence on the page states:

"It incorporates FtpFileOpen, FileCreate, InternetReadFile, WriteFile, InternetCloseHandle, and CloseHandle calls."

However, the function FileCreate does not exist. It should be CreateFile.

Credit: Kim Gräsman


Submissions:

I am no longer accepting errata submissions, as I have no further time to update this page. Thank you all for your submissions and attention. I hope this page has helped.

History:

I bought Programming Windows, 5th Edition to learn Windows programming, and was quite excited to see that it was about 1,500 pages, since I knew that meant it would teach it in depth, and with intimate precision, which is how I love to learn. I need to know how things work at the lowest level. This desire for low level workings is what attracted me to assembly language ever since I first programmed in the 2nd grade, and it also brought my attention to the 'assembly language' of Windows, the Win32 API -- the low level functions that are handling all that neat GUI stuff in the background.

So as I traversed the book with great attention, took everything in, and tested it all to make sure I understood it at the root level. I extended every example and concept to beyond what the book stated, to make sure all my understandings were solid. I did not merely want to get something working on the screen. By doing so, I ran across a number of errors and issues within the book. While this list of errors is extensive, many are minor, and I do not believe that I have done anything special. Anyone with the proper attention to learn the issues at hand, for real, will arrive at the same conclusions.

For each issue, I sent Charles an email. I didn't expect this, but soon I had sent over 50 of these emails. I finally reported a potentially critical issue with a core construct: the Message Loop. This is intrinsic to not only every Windows program in the book, but to every Windows program ever made. As this book is the bible to Windows programming -- Charles is one of only seven people (and the only author) to receive the Windows Pioneer Award for his work -- it means a lot of Windows programs were made using it and prior versions. So this disturbed him, and I was concerned greatly.

Essentially, the GetMessage() function within the Message Loop is not checked for errors. Consider that even several MSDN pages also gave examples that did not check it for errors. This was in complete contradiction to the GetMessage() page itself, which explicitly states to avoid exactly such code! The issue is that what is 'left' in the "msg" variable when there is an error is undefined, thus code that doesn't check errors will translate and dispatch unknown data. (You can read more about my analysis of what you should do with in my errata.)

In any case, Charles wrote me back after that doozie, and stated that if I made a public page with my errata, that he would link to my errata page from his, which he did. He then sent me a copy of his new Applications = Code + Markup book signed with a humorous message:



TO JASON DOUCETTE,
   THE INDEFATIGABLE CHRONICLER
        OF MY BLUNDERS.
       CHARLES PETZOLD

Take care,
Jason Doucette

Click to purchase Programming Windows, Fifth Edition by Charles Petzold
Click to Purchase Book


Back to Main Resume Page

[Jason Doucette]: Traditional Resume / Curriculum VitaeProjects / GamesReal Time Computer GraphicsArtificial IntelligenceWorld Records / 196 Palindrome QuestWallpapers / Desktops / BackgroundsUniversity TranscriptsProgramming Windows, Fifth Edition, Errata Addendum

53,128 visitors since July 21st, 2003
1,706,626 total page views since May 13th, 1999
Jason Allen Doucette / Xona Games