• The Old New Thing

    Homework assignment about window subclassing

    • 6 Comments

    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
    SubclassWindow(hwnd, OldWndProc);
    
    What could go wrong? We'll discuss it tomorrow.
  • The Old New Thing

    How much do you expect from a framework layer?

    • 10 Comments

    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.

    So if you try to set your ToolTip.AutoPopDelay to something longer, like 60 seconds, you will find that the delay time is not properly set, since the ToolTip class merely passes the delay value through to the underlying control. And until you understood the underlying control, you would never understand why.
  • The Old New Thing

    A warning to people averse to code

    • 0 Comments
    This is going to be a code-heavy week. People who are averse to code may want to just lie low until next week. I'll try to make it up by having next week be more storytelling-focused.
  • The Old New Thing

    Returning values from a dialog procedure

    • 15 Comments

    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 of information:

    • Was the message handled?
    • If so, what should the return value be?

    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 window long.

    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);
        return 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)").

  • The Old New Thing

    Little facts you didn't know about concrete

    • 7 Comments
    Concrete is stronger once it has hardened. Thanks, Associated Press! I learn something new every day.
  • The Old New Thing

    Why are companies so worried about retraining costs?

    • 27 Comments

    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 email?

  • The Old New Thing

    An anecdote about improper capitalization

    • 8 Comments

    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 about this.

    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 compared case-insensitively.

  • The Old New Thing

    Just follow the rules and nobody gets hurt

    • 16 Comments

    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.

    Until now.

    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 warned.

  • The Old New Thing

    The long and sad story of the Shell Folders key

    • 43 Comments

    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 function. 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 working.

    For example, did you know that if you never open your Fonts folder, and if no program ever calls SHGetSpecialFolderLocation(CSIDL_FONTS), then 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.

    I strongly suspect that of those four original programs for which the "Shell Folders" key was originally created, not a single one is still in existence today.
  • The Old New Thing

    Why highlighting by inverting colors is a bad idea

    • 14 Comments

    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.

     
     
    OneTwo

    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.

    Even if your eyes happen to be good enough to spot the difference, it's still not a very obvious difference.
Page 415 of 427 (4,263 items) «413414415416417»