Holy cow, I wrote a book!
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
When you are attempting to architect an operating system,
backwards compatibility is one of the ones you just have to accept.
But when new programs rely on app hacks designed for old
programs, that makes you want to scream.
Once upon a time, in what seems like a galaxy far far away
(a Windows 95 beta release known as "M3"), we documented
a registry key called "Shell Folders" that programs could read
to obtain the locations of various special folders like
the Fonts folder or the My Documents folder.
The developers who received Windows 95 M3 Beta followed the
documentation and used that key.
In the meantime, Windows 95 work continued, and we realized that
a registry key was the wrong place to store this information.
In part, because a lot of things (like the Control Panel) aren't
disk directories so they wouldn't be expressible there. And
in another part, because we had forgotten to take into
account a feature of Windows NT called
roaming user profiles, where your user profile can move around
from place to place, so a hard-coded path in the registry is no good.
So we created the function SHGetSpecialFolderLocation,
and updated the documentation to instruct developers to use this new
function to obtain the locations of various special folders.
The documentation on the old "Shell Folders" key was removed.
But to ease the transition from the M3 documentation to the RTM
documentation, we left the old "Shell Folders" registry key
around, "temporarily", but it was no longer the location where
this information was kept. It was just a shadow of the "real"
data stored elsewhere ("User Shell Folders").
We shipped Windows 95 RTM with this "temporary" key, because
there were still a small number of programs (let's
say four) that hadn't finished
converting to the new SHGetSpecialFolderLocation
But the support for this registry key was severely scaled back,
so it was just barely good enough for those four
programs. After all, this was just a backwards compatibility hack.
All new programs should be using SHGetSpecialFolderLocation.
In other words, the "Shell Folders" key exists solely to permit
four programs written in 1994 to continue running on the RTM version
of Windows 95.
You can guess what happened next.
Windows 95 came out and everybody and their brother wanted to write
programs for it. But reading documentation is a lot of work.
So when there's some setting you want to retrieve, and you don't
want to read documentation, what do you do? You search the registry!
(Sound familiar? People still do this today.)
So now there were hundreds, thousands of programs which didn't
call SHGetSpecialFolderLocation; they just went
directly for the "Shell Folders" key.
But they didn't realize that the support for "Shell Folders"
was only barely enough to keep those four original programs
For example, did you know that if you never open your
Fonts folder, and if no program ever calls
there will not be a "Fonts" entry in the "Shell Folders" key?
That's because those entries are created only if somebody asks for
them. If nobody asks for them, then they aren't created.
No point setting up an app hack until it is needed.
Of course, when you're testing your program, you don't reformat
your hard disk, install Windows 95 clean, then run your program.
You just put your program on a Windows 95 machine that has been
running for months and see what happens.
And what happens is that, since at some point during all those
months you opened your Font folder at least once, the "Fonts"
entry exists and you are happy.
And then back in our application compatibility labs, your program
gets a "Fail" grade because our lab reformats the computer before
installing each application to make sure there is nothing left
over from the previous program before installing the next one.
And then the core development team gets called in to figure out why
this program is getting a "Fail" grade, and we find out that in fact
this program, when faced with a freshly-formatted machine,
never worked in the first place.
Philosophical question: If a program never worked in the first place,
is it still a bug that it doesn't work today?
Now there are those of you who are licking your lips and saying,
"Wow, there's this 'User Shell Folders' key that's even cooler
than the 'Shell Folders' key, let me go check it out."
I implore you to exercise restraint and not rely on this new key.
Just use the function SHGetFolderPath, which returns
the path to whatever folder you want.
Let the "User Shell Folders" key rest in peace.
Because in Longhorn, we're doing even more stuff with user profiles
and I would personally be very upset if we had to abandon
the "User Shell Folders" key as "lost to backwards compatiblity"
and set up shop in a new "Real User Shell Folders" key.
Often people will say, "Oh, we can highlight by inverting the color."
This may have worked great on two-color black-and-white displays, but in the world
of 32-bit color this no longer is effective.
Consider the following picture. See if you can guess which one is inverted.
Answer: The one on the right is inverted.
The background color is gray, #808080. The box labelled "two" is highlighted by inversion.
The inverse of #808080 is #7F7F7F, which is practically the same color.