• The Old New Thing

    Welcome Martyn Lovell

    • 0 Comments
    Martyn Lovell from the VC++ team has joined the world of blogging. Martyn is a smart guy, even though he does spell some words funny.
  • The Old New Thing

    Safer subclassing

    • 5 Comments

    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:

    ...
    case WM_NCDESTROY:
      RemoveWindowSubclass(hwnd, thisfunctionname, uIdSubclass);
      return DefSubclassProc(...);
    

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

    There was another comment that claimed that the SubclassWindow macro is undocumented. Actually this macro is so old that the documentation for it has faded almost into obscurity. (You forced my hand; I wasn't going to dig into this header file until tomorrow!)
  • 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.

Page 429 of 441 (4,405 items) «427428429430431»