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:
- 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.
- 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:
- wParam: The high-order word specifies the notification code.
- wParam: The low-order word specifies the identifier of the button.
- 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:
- The right mouse button was clicked and released.
- Shift+F10 was pressed.
- 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 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:
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':
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:
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':
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:
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:
- BITMAPFILEHEADER structure
- BITMAPINFOHEADER structure
(or BITMAPCOREHEADER structure
for older OS/2-compatible BMP files)
- 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.)
- 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:
- The BITMAPFILEHEADER is 14 bytes.
- The BITMAPINFOHEADER is 40 bytes (if this were an OS/2-compatible BMP, this
structure would be the BITMAPCOREHEADER structure, which is 12 bytes).
- There is no color table, as the color data is stored in the bitmap pixel data itself.
- 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
|
|
|
|
|