August, 2011

  • The Old New Thing

    Menu item states are not reliable until they are shown because they aren't needed until then

    • 10 Comments

    A question arrived from a customer (with the rather unhelpful subject line Question for Microsoft) wondering why, when they call Get­System­Menu and then ask for the states of the various menu items like SC_MINIMIZE, the menu item states don't reflect reality. The menu item states don't synchronize with reality until the user actually opens the system menu.

    There is no requirement that applications keep menu item states continuously in sync. After all, that's why we have messages like WM_INIT­MENU: To tell the application, "Whoa, we're about to show this menu, so you might want to comb its hair and pick the food out of its teeth so it can be seen by the user." Lazy evaluation is common, because maintaining states continuously can be expensive, and there's no point constantly turning items on and of and on and off if the user can't see them anyway.

    This is double-true for system menus, because maintaining the states continuously is not possible when the system menu is being shared across windows. The menu states are not synchronized to the window until the menu is about to be displayed.

    If you want to know whether the SC_MINIMIZE menu item would be enabled if the menu were shown, you can check the window styles: A window can be minimized if it has a WS_MINIMIZE­BOX and is not already WS_MINIMIZEd. Similar logic can be applied to the other menu items.

    Well, except for SC_CLOSE. While in most cases the window caption determines what is enabled on the menu, the Close button works backward: It is the state of the menu item that controls whether the Close button is enabled. So in the special case of SC_CLOSE, you can query the state at any time, because for that case, the menu controls the state rather than simply reflecting it.

    Why is SC_CLOSE so special? Here come da history.

    The Close button was added in Windows 95. Since versions of Windows prior to Windows 95 didn't have a Close button, they didn't need a style to specify whether the Close button should be enabled or not. (You don't need a style to control something that doesn't exist.) Windows 95 added the Close button and hooked it up to the only thing that it had available, namely, the SC_CLOSE item on the system menu. Sure, Windows 95 could have have invented a new window style, but since SC_CLOSE already existed and applications were already using it, using SC_CLOSE to control the Close button allowed old applications to reap the benefits of the new Close button automatically. It also meant that there was one less thing you had to change when porting your program to Windows 95.

    Bonus chatter: You can now answer Alex Cohn's question:

    I wonder if the EnableMenuItem method will work for minimize and maximize, too. After all, these buttons also have siblings in the Alt-space menu.
  • The Old New Thing

    Why doesn't \b match word boundaries correctly?

    • 21 Comments

    A colleague of mine was having trouble getting the \b metacharacter in a regular expression to work. Of course, when somebody asks a question like that, you first have to establish what their definition of "work" is. Fortunately, he provided some examples:

    Regex.IsMatch("foo", @"\b" + @"foo" + @"\b") true
    Regex.IsMatch("%1" , @"\b" + @"%1"  + @"\b") false
    Regex.IsMatch("%1" , @"\b" + @"\%1" + @"\b") false
    Regex.IsMatch("%1" , @"\b" + @"\%1" + @"\b") false
    Regex.IsMatch("%1" , @"..") true
    Regex.IsMatch("%1" , @"%1") true

    "The last two entries are just sanity checks to make sure I didn't make some stupid mistake like passing the parameters in the wrong order. I want to search for a string that contains %1 with word boundaries on either side, something I would normally use \b for. Is there something special about the % character? Notice that the match succeeds when I look for the word foo."

    Everything is working as it should. Recall that the \b metacharacter matches when there is a \w on one side and a \W on the other, where the beginning and end of the string are treated as if they were \W.

    The string %1 therefore breaks down as

    virtual \W beginning of string
    \W % is not an alphanumeric or _
    \w 1 is a digit
    virtual \W end of string

    The only points where \b would match are immediately before and after the 1, since those are the transition points between \w and \W and vice versa. In particular, the location immediately before the percent sign does not match since it is surrounded by \W on both sides.

    My colleague responded, "D'oh! I keep forgetting that % won't act like a \w just because I want it to."

  • The Old New Thing

    A shell extension is a guest in someone else's house; don't go changing the code page

    • 26 Comments

    A customer reported a problem with their shell extension:

    We want to format a floating point number according to the user's default locale. We do this by calling snprintf to convert the value from floating point to text with a period (U+002E) as the decimal separator, then using Get­Number­Format to apply the user's preferred grouping character, decimal separator, etc. We found, however, that if the user is running in (say) German, we find that sometimes (but not always) the snprintf function follows the German locale and uses a comma (U+002C) as the decimal separator with no thousands separator. This format prevents the Get­Number­Format function from working, since it requires the decimal separator to be U+002E. What is the recommended way of formatting a floating point number according to the user's locale?

    The recommended way of formatting a floating point number according to the user's locale is indeed to use a function like snprintf to convert it to text with U+002E as the decimal separator (and other criteria), then use Get­Number­Format to apply the user's locale preferences.

    The snprintf function follows the C/C++ runtime locale to determine how the floating point number should be converted, and the default C runtime locale is the so-called "C" locale which indeed uses U+002E as the decimal separator. Since you're getting U+002C as the decimal separator, somebody must have called set­locale to change the locale from "C" to a German locale, most likely by passing "" as the locale, which means "follow the locale of the environment."

    Our shell extension is running in Explorer. Under what conditions will Explorer call set­locale(LC_NUMERIC, "")? What should we do if the locale is not "C"?

    As it happens, Explorer never calls set­locale. It leaves the locale set to the default value of "C". Therefore, the call to snprintf should have generated a string with U+002E as the decimal separator. Determining who was calling set­locale was tricky since the problem was intermittent, but after a lot of work, we found the culprit: some other shell extension loaded before the customer's shell extension and decided to change the carpet by calling set­locale(LC_ALL, "") in its DLL_PROCESS_ATTACH, presumably so that its calls to snprintf would follow the environment locale. What made catching the miscreant more difficult was that the rogue shell extension didn't restore the locale when it was unloaded (not that that would have been the correct thing to do either), so by the time the bad locale was detected, the culprit was long gone!

    That other DLL used a global setting to solve a local problem. Given the problem "How do I get my calls to snprintf to use the German locale settings?" they decided to change all calls to snprintf to use the German locale settings, even the calls that didn't originate from the DLL itself. What if the program hosting the shell extension had done a set­locale(LC_ALL, "French")? Tough noogies; the rogue DLL just screwed up the host program, which wants to use French locale settings but is now being forced to use German ones. The program probably won't notice that somebody secretly replaced its coffee with Folgers Crystals. It'll be a client who notices that the results are not formatted correctly. The developers of the host program, of course, won't be able to reproduce the problem in their labs, since they don't have the rogue shell extension, and the problem will be classified as "unsolved."

    What both the rogue shell extension and the original customer's shell extension should be using is the _l variety of string formatting functions (in this case _snprintf_l, although _snprintf_s_l is probably better). The _l variety lets you pass an explicit locale which will be used to format that particular string. (You create one of these _locale_t objects by calling _create_locale with the same parameters you would have passed to set­locale.) Using the _l technique solves two problems:

    1. It lets you apply a local solution to a local problem. The locale you specify applies only to the specific call; the process's default locale remains unchanged.
    2. It allows you to ensure that you get the locale you want even if the host process has set a different locale.

    If either the customer's DLL or the rogue DLL had followed this principle of not using a global setting to solve a local problem, the conflict would not have arisen.

  • The Old New Thing

    An even easier way to get Windows Media Player to single-step a video

    • 17 Comments

    Since my original article explaining how to get Windows Media Player to single-step a video, I've learned that there's an even easier way.

    • Pause the video.
    • To single-step forward, Ctrl+Click the Play button.
    • To single-step backwrd, Ctrl+Shift+Click the Play button.

    Backward-stepping is dependent upon the codec; some of them will go backward to the previous keyframe.

    The person who tipped me off to this feature: The developer who implemented it.

    Remember: Sharing a tip does not imply that I approve of the situation that led to the need for the tip in the first place.

  • The Old New Thing

    Microspeak: Dogfood

    • 28 Comments

    Everybody knows about the Microspeak term dogfood. It refers to the practice of taking the product you are working on and using it in production.¹ For the Windows team, it means installing a recent build of Windows on your own computer as well as onto a heavily-used file server. For the Office team, it means using a recent build of Office for all your documents. For the Exchange team, it means moving the entire product team to a server running a recent build of Exchange. You get the idea.

    Purists would restrict the use of the word dogfood to refer to a product group using its own product, but in practice the meaning has been generalized to encompass using a prerelease product in a production environment. The Windows team frequently dogfoods recent builds of Office and Exchange Server. Actually, the Exchange Server case is one of double-dogfood,² for not only is the server running a prerelease version of Exchange Server, it's doing so atop a prerelease version of Windows!

    Dogfooding does have its costs. For example, the prerelease version of Exchange Server might uncover a bug in the prerelease version of Windows. While the problem is investigated, the Windows division can't send email. These outages are comparatively rare, although they are quite frustrating when they occur. But you have to understand that the whole purpose of dogfooding is to find exactly these sorts of problems so that our customers won't!

    Footnote

    ¹ Despite the efforts of our CIO, the term ice-creaming has not caught on.

    ² I made up the term "double-dogfood" just now. It is not part of Microspeak.

  • The Old New Thing

    Why can you set each monitor to a different color depth?

    • 31 Comments

    Random832 seemed horrified by the fact that it is possible to run multiple monitors, with different color formats on each monitor. "Seriously, why does it let you do that?"

    Well, of course you can do that. Why shouldn't it let you do that?

    When multiple monitors were introduced to Windows, video hardware was nowhere near as advanced as it is today. One common discovery was that your computer, which came with a video card in one of the expansion slots, actually had a video chip baked into the motherboard, but which was disabled in the BIOS. In other words, your computer was actually multiple-monitor-capable; it's just that the capability was disabled.

    Once you got it enabled, you would discover that the onboard video adapter was not as good as the one in the expansion slot. (Duh. If it were as good as the one in the expansion slot, then the manufacturer would have saved a lot of money and not bothered shipping a video card in the expansion slot!) Usually, the onboard video card didn't have a lot of video RAM. You still want to run it at 1024×768 (hey, that's high resolution for these days), but in order to do that, you need to reduce the color depth. On the other hand, the card in the expansion slot has a huge amount of video RAM (four megabytes!), so you take advantage of it by running at a higher color depth.

    You're now getting the most out of your machine; each video card is cranked up as high as it can go. (The lame-o card doesn't go very high, of course.) What could possibly be wrong with that?

    Bonus chatter: It so happened that some of these "secret video card" motherboards had a feature where they would disable the ROM BIOS on the on-board video card if they detected a plug-in video card. To get multi-monitor support on these recalcitrant machines, one of my colleagues wrote a tool that you used like this:

    • Turn off the computer and remove the plug-in video card.
    • Boot the computer with only the lame-o on-board video.
    • Run the tool, which captures the ROM BIOS.
    • Turn off the computer, put the plug-in video card back in, and boot the computer again.
    • Run the tool again and tell it "Hey, take that ROM BIOS you captured and map it into memory where a ROM BIOS would normally go, and then initialize the video card from it and add it as my second monitor."

    It was a bit of a hassle, but it let you squeak crappy multi-monitor support out of these lame-o motherboards.

Page 3 of 3 (26 items) 123