Holy cow, I wrote a book!
So what was wrong with our
little subclassing sketch?
Most people figured this out.
Consider what would happen if somebody else had subclassed the window during the "...
do stuff ..." section. When we unsubclassed the window, we would have removed two subclasses,
the one we installed, and the one that was installed after us. If the other subclass
allocated memory (which is very common), then that memory got leaked, in addition
to the subclass failing to do whatever it was trying to do.
Do not assume that subclasses are added and removed in a purely stack-like manner.
If you want to unsubclass and find that you are not the window procedure at the top
of the chain you cannot safely unsubclass. You will have to leave
your subclass attached until it becomes safe to unsubclass. Until that time, you just
have to keep passing the messages through to the previous procedure.
This is quite a cumbersome process, so the shell team wrote some helper functions
to do all this for you. The SetWindowSubclass
function does all the grunt work of installing a subclass procedure, remembering
the previous one, and passing reference data to the subclass procedure you provide.
You use the DefSubclassProc
function to forward the message to the previous subclass procedure, and when you're
done, you use the RemoveWindowSubclass
function to remove yourself from the chain. RemoveWindowSubclass does all the
work to do the right thing if you are not the window procerure at the top of the chain.
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.
This is typically done either by removing the subclass once your temporary need has
passed, or if you are installing a permanent subclass, by inserting a call to RemoveWindowSubclass
inside the subclass procedure itself:
RemoveWindowSubclass(hwnd, thisfunctionname, uIdSubclass);
One comment expressed concern that a message could be sent between the call to SubclassWindow and
the store of the previous window procedure into the OldWndProc variable.
This is actually a safe operation provided that you are doing the work from the thread
that owns the window you are subclassing. Remember that message delivery occurs only
when the thread is in a receiving state, such as when it calls GetMessage or PeekMessage.
If somebody sends a message when the thread is not in a receiving state, the message
merely waits until the thread finally calls GetMessage (for example)
before being delivered. Since we don't make any message-receiving function calls between
the SubclassWindow and the store into OldWndProc, there
is no risk of an untimely message arriving before the store to OldWndProc has
Window subclassing is trickier than you think.
Consider this code sketch:
// Subclass the window for a little while
WNDPROC OldWndProc = SubclassWindow(hwnd, NewWndProc);
... do stuff ...
// Okay all done, remove our subclass by
// restoring the previous window procedure
If a framework exposes functionality provided by a lower layer, how hard should the
framework try to insulate you from all the quirks and limitations of the lower layer?
Instinctively, of course, you would say, "The framework should insulate me completely."
But be careful what you ask for. If a framework insulated you completely, then every
limitation of the underlying layer needs to be worked around in some manner or other.
This would mean writing a lot of code to emulate missing functionality or removing
a limitation, just in case somebody using the framework actually runs into that limitation.
Let's take for example the
ToolTip.AutoPopDelay property. The
ToolTip class is a Windows Forms wrapper around the
Common Controls ToolTip window class. If you look at the
documentation for the TTM_SETDELAYTIME message, you'll see that the delay time
(iTime) is passed in the low word of the lParam parameter. Consequently,
it is limited to a 16-bit value, and in this case, it's a signed 16-bit value since
negative values for iTime have special meaning (as noted in the documentation).
Since the maximum value for a signed 16-bit integer is 32767, the maximum value you
can set for the delay time is a little over 32 seconds.
For some reason, the way values are returned from a dialog procedure confuses people,
so I'm going to try to explain it a different way.
The trick with dialog box procedures is realizing that they actually need to return two pieces
Since two pieces of information have to be returned, but a C function can have only
one return value, there needs to be some other way to return the second piece of information.
The return value of the dialog procedure is whether the message was handled. The second
piece of information - what the return value should be - is stashed in the DWLP_MSGRESULT
In other words, DefDlgProc goes something like this:
LRESULT CALLBACK DefDlgProc(
HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
DLGPROC dp = (DLGPROC)GetWindowLongPtr(hdlg, DWLP_DLGPROC);
SetWindowLongPtr(hdlg, DWLP_MSGRESULT, 0);
BOOL_PTR fResult = dp(hdlg, uMsg, wParam, lParam);
if (fResult) return GetWindowLongPtr(hdlg, DWLP_MSGRESULT);
else ... do default behavior ...
If you return anything other than 0, then the value you set via SetWindowLongPtr(hdlg,
DWLP_MSGRESULT, value) is used as the message result.
For example, many WM_NOTIFY notifications allow you to override default behavior by
returning TRUE. To prevent a listview label from being edited, you can return TRUE
from the LVN_BEGINLABELEDIT notification. But if you are doing this from a dialog
procedure, you have to do this in two steps:
SetWindowLongPtr(hdlg, DWLP_MSGRESULT, TRUE);
The second line sets the return value for the dialog procedure, which tells DefDlgProc
that the message has been handled and default handling should be suppressed. The first
line tells DefDlgProc what value to return back to the sender of the message (the
listview control). If you forget either of these steps, the desired value will not
reach the listview control.
Notice that DefDlgProc sets the DWLP_MSGRESULT to zero before sending
the message. That way, if the dialog procedure neglects to set a message result explicitly,
the result will be zero.
This also highlights the importance of calling SetWindowLongPtr immediately before
returning from the dialog procedure and no sooner. If you do anything between setting
the return value and returning TRUE, that may trigger a message to be sent to the
dialog procedure, which would set the message result back to zero.
Caution: There are a small number of "special messages" which do not follow
this rule. The list is given in the
documentation for DialogProc. Why do these exceptions exist? Because when the
dialog manager was first designed, it was determined that special treatment for these
messages would make dialog box procedures easier to write, since you wouldn't have
to go through the extra step of setting the DWLP_MSGRESULT. Fortunately, since those
original days, nobody has added any new exceptions. The added mental complexity of
remembering the exceptions outweigh the mental savings of not having to write one
line of code ("SetWindowLongPtr(hdlg, DWLP_MSGRESULT, desiredResult)").
Remember, most people do not view the computer as a world to be explored. It is merely
a means to an end. So they learn the five steps they need to follow, and if they can't
do them, they get stuck. "I hit Alt+Tab like I always do, to switch to another program,
but instead of switching, this strange window showed up. Help!" Or "I print the document
by clicking the Actions menu and selecting Print, but now there is no Actions menu.
Help!" Changes to the user interface also mean that screenshots need to be re-taken
and training materials reprinted.
In a sense, people act computerlike when they are in front of a computer!
I behaved the same way when I was working at product support earlier this year. The
Product Support division has a system for tracking calls and issues, and we were given
a handout with instructions like, "To transfer an issue to XYZ, click Transfer, then
select XYZ from the list." Now imagine if the Transfer button weren't there any more
(maybe it got moved to a sub-dialog) or if XYZ was no longer on the list (perhaps
it got combined with QRS). I would have been stuck. I'm not going to go hunting around
looking for the Transfer button; if I click the wrong button I might create a corrupted
record in their database and create more problems than I was trying to solve. So when
I couldn't follow the instructions, I called for help.
There are people who argue that, "Well, in order to use a computer, you should be
required to learn how a computer works." Pshaw. I drive a car and yet I don't know
how a carburetor works, what the optimum fuel/air ratio is, or even how many cylinders
my engine has. I don't care. All that matters to me is that I step on the pedal and
it goes. We don't require people to be auto mechanics before they get a drivers license
(at least, not in the United States; other countries may be different). Why should
we expect them to understand how a computer works before they are allowed to send
I've already discussed some
of the strange consequences of case-sensitive comparisons.
Joe Beda mentioned
the Internet Explorer capitalization bug that transformed somebody's name into a dead
body. Allow me to elaborate. You might learn something.
This bug occurred because Internet Explorer tried to capitalize the characters in
the name "Yamada" but was not mindful of the character-combining rules of the double-byte
932 character set used for Japanese. In this character set, a single glyph can be
represented either by one or two bytes. The Roman character "A" is represented by
the single byte 0x41. On the other hand, the characters "の" is represented
by the two bytes 0x82 0xCC. (You will need to have Japanese fonts installed to see
the "no" character properly.)
When you parse a Japanese string in this character set, you need to maintain state.
If you see a byte that is marked as a "DBCS lead byte", then it and the byte following
must be treated as a single unit. There is no relationship between the character represented
by 0xE8 0x41 (錢) and 0xE8 0x61 (鐶) even though the second bytes happen
to be related when taken on their own (0x41 = "A" and 0x61 = "a").
Internet Explorer forgot this rule and merely inspected and capitalized each byte
independently. So when it came time to capitalize the characters making up the name
"Yamada", the second bytes in the pairs were erroneously treated as if they were Roman
characters and "capitalized" accordingly. The result was that the name "Yamada" turned
into the characters meaning "corpse" and "field". You can imagine how Mr. Yamada felt
Converting the string to Unicode would have helped a little, since the Unicode capitalization
rules would certainly not have connected two unrelated characters in that way. But
there are still risks in character-by-character capitalization: In some languages,
capitalization is itself context-sensitive. MSDN
gives as an example that in Hungarian, "SC" and "Sc" are not the same thing when
You may have been lazy and not bothered calling VirtualProtect(PAGE_EXECUTE) when
you generated some code on the fly. You got away with it because the i386 processor
page protections do not have a "read but don't execute" mode, so anything you could
read you could also execute.
Starting with Windows XP Service Pack 2, on processors which support it (according
to the web page, currently AMD K8, Itanium, and AMD64), the stack and heap will not
be executable. If you try to execute the stack or the heap, an exception will be raised
and the code will not execute. In other words, execute page protection will soon be
enforced, now that processors exist that support it. (Actually, I believe Windows
XP for Itanium already used this new protection level, so those of you who have been
playing around with your Itanium may have seen this already.)
If you were a good developer and followed the rules on page protections, then this
has no effect on you. But if you cheated the rules and took advantage of specific
hardware implementation details, you may find yourself in trouble. Consider yourselves