June, 2012

  • The Old New Thing

    It's not a good idea to give multiple controls on a dialog box the same ID


    When you build a dialog, either from a template or by explicitly calling Create­Window, one of the pieces of information about each control is a child window identifier. And it's probably in your best interest to make sure two controls on the dialog don't have the same ID number.

    Of course, one consequence of giving two control the same ID number is that the Get­Dlg­Item function won't know which one to return when you say, "Please give me control number 100." This naturally has a cascade effect on all the other functions which are built atop the Get­Dlg­Item function, such as Set­Dlg­Item­Int.

    Another reason to avoid duplication is that many notification messages include the control ID, and if you have a duplicate, you won't know which one generated the notification. Okay, this isn't actually the case, because the notification messages typically also include the window handle, so you can use the window handle to distinguish between your two controls both with ID=100. Though it means that you can't use a simple switch statement any more.

    (See sidebar for discussion of when duplicate IDs are acceptable.)

    Most of the time, you get away with the duplicate IDs because you can use the window handle to distinguish them. But there is one notable case where duplicate IDs cause problems: Identifying the default pushbutton on the dialog.

    One of the things that the dialog manager does when it builds a dialog box from a template is keep an eye out for a button control with the BS_DEF­PUSH­BUTTON style. When it finds one, it remembers the control ID so that it can restore it as the default pushbutton when focus is on a non-pushbutton control. (When focus is on a pushbutton, then that button becomes the default pushbutton.)

    The dialog manager records the initial default pushbutton by sending itself the DM_SET­DEF­ID message, and the default handler merely records the value in a safe place so it can return it when somebody sends the DM_GET­DEF­ID message. You can send the DM_SET­DEF­ID message yourself if you want to change the default pushbutton, and that's where the trouble comes in.

    The only parameter to the DM_SET­DEF­ID message is the ID of the dialog control you want to be the new default, so if your dialog box has two controls with that ID, then you've created a bit of a problem. When the user hits the Enter key, the dialog manager wants to fire a WM_COMMAND notification for the default button, but it sees two of them and gets confused.

    Actually, it doesn't really get confused. It just picks one of them arbitrarily and ignores the other one.

    And then confusion sets in.

    If the two buttons with conflicting IDs do different things, then your code which receives the WM_COMMAND notification may end up seeing the notification coming from the wrong control. For example, suppose you inadvisedly assign ID number 100 to both the Reformat and Scan buttons (and out of an abundance of caution, set Scan as the default pushbutton). When the user hits Enter, the dialog manager sends a WM_GET­DEF­ID message to say, "Hey, what's the default pushbutton?" The message returns 100, and now the dialog manager is stuck saying to itself, "Um, there are two 100's. Eeny, meeny, miny, moe. Okay, it's the Reformat button." Boom, hard drive reformatted.

    From the same dialog template, suppose you realize, "Oh, I don't want to let the user reformat the hard drive until they've entered a volume label," so you disable the Reformat button if the volume label field is blank. The user hits Enter, and remember, you set Scan as the default button. But since Reformat and Scan have the same control ID, the dialog manager once again plays eeny-meeny-miney-moe, and say it picks the Reformat button. But it also sees that the Reformat button is disabled, so it just beeps.

    And then your user wonders why, when they hit Enter and the Scan button is the default pushbutton, it doesn't scan but instead just beeps.

    Okay, all this discussion seems pretty obvious, doesn't it, but as we dig deeper into the dialog manager, you'll see how the principle of "Don't create a dialog box with conflicting dialog control IDs" has perhaps unexpected consequences.

    Sidebar: If the duplications are all among controls that do not raise notifications and which you do not need to identify programmatically, then you're not going to run into much trouble at all. By convention, the "control ID for controls where I don't care about the ID" is −1, although you can use any number you like; the window manager doesn't care, as long as it doesn't collide with the ID of a control that you do care about.

    Note that some resource management tools (such as translation toolkits, or interactive dialog editors) assume that there are no duplicate IDs aside from the special don't-care value −1, so if you're going to use duplicate IDs because you don't care, you'd be well-served to stick to the −1 convention.

    Bonus chatter: Why doesn't DM_SET­DEF­ID take a window handle instead of a control ID? That would solve the problem of multiple controls with the same ID, since you now have the window handle, which identifies the control uniquely.

    Yeah, it could've done that. Though it would also have created problems if the default pushbutton is destroyed, and that happens more often than you think.

    Remember back in the early 16-bit days, we didn't have parameter validation, so a dangling window handle meant that you crashed when you tried to use it. (Or worse, the window handle could have been reused for another totally unrelated window! Window handle reuse was much more common in 16-bit Windows.) Mapping the window handle back to an ID and then converting the ID to a window on demand meant that you never keep a window handle around, which means that you never have to worry about the handle going bad.

    Making the DM_SET­DEF­ID message handle-based would also make it harder for somebody to pull the "Create two controls with the same ID but hide one of them at runtime" trick described above, because they would also have to remember to send a hypothetical DM_SET­DEF­HWND message whenever they pulled the switcheroo.

    And besides, the only people this design change helps out are people who put multiple visible controls on a dialog box with the same ID. You don't optimize for the case where somebody is mis-using your system.

  • The Old New Thing

    What is the history of the GetRandomRgn function?


    An anonymous commenter was curious about how the Get­Random­Rgn function arrived at its strange name, what the purpose of the third parameter is, and why it is inconsistent between Windows 95 and Windows NT.

    The sense of word "random" here is hacker slang rather than its formal probabilistic definition, specifically sense 2: "Assorted, undistinguished", perhaps with a bit of sense 4 sprinkled in: "Not well organized." (Commenter Gabe suggested that a better name would have been Get­Specific­Rgn.)

    Once upon a time, when men were men and Windows was 16-bit, there was an internal function used to communicate between the window manager and GDI in order to set up device contexts. Internally, the region was called the Rao Region, named after Rao Remala, the programmer who invented it, and the function that calculated the Rao Region was rather uncreatively called Compute­Rao­Rgn.

    When porting to 32-bit Windows, the Windows NT and Windows 95 teams both found that they needed this same internal communication between the window manager and GDI. GDI already has a bunch of functions named Get­Xxx­Rgn, so instead of writing a separate marshaler for each one, they opted to write a single Get­Random­Rgn function which takes an integer which serves as a function code, specifying which region the caller actually wants. (I suspect the Windows 95 team followed the cue of the Windows NT team, since Windows NT ran into the problem first.)

    Since this was an internal function, it didn't matter that the name was a bit cutesy, nor did it matter what coordinate system it used, as long as the window manager and GDI agreed on the name and coordinate system. The Windows 95 team still had a lot of 16-bit code that they needed to be compatible with, so they chose to generate the Rao region the same way that the 16-bit Compute­Rao­Rgn function did it. The Windows NT folks, on the other hand, decided that it was more convenient for them that this internal function use screen coordinates, so that's what it returns on Windows NT.

    Get­Random­Rgn isn't really a function that was designed to be public. It was just an internal helper function that outsiders discovered and relied upon to the point that that it became a compatibility constraint so strong that it turned into a de facto documented function. And all the weirdness you see behind it is the weirdness of a function never intended for public consumption.

    The introduction of the Desktop Window Manager in Windows Vista changed the way the visible region was managed (since all windows are logically visible even when occluded because their drawing is redirected to an off-screen surface), but the Get­Random­Rgn function has to keep track of the "visible region" anyway, for compatibility.

  • The Old New Thing

    Globalization quiz: In honor of, well, that's part of the quiz

    The Corporate Citizenship Tools; Microsoft Local Language Program Web site contains a map of the world, coded by region. There was a bug on the map. See if you can spot it:

      Middle East & Africa
      North America
      South America
      South Pacific

    After I pointed out the error, they fixed the map on their Web page, so no fair clicking through to the Local Language Program Web page and comparing the pictures!

    Non-useful hint: I chose the publication date of this quiz in honor of the answer.

    Bonus chatter: Inside the answer.

  • The Old New Thing

    Now that Windows makes it harder for your program to block shutdown, how do you block shutdown?


    Up until Windows XP, applications could intercept the WM_QUERY­END­SESSION message and tell Windows, "No, don't shut down." If they were polite about it, they would also inform the user which application blocked system shutdown and why. And if they were really polite about it, they would even provide a way for the user to say, "I don't care; shut down anyway."

    As I noted some time ago, Windows Vista made it harder for applications to block shutdown. Applications are given two seconds to clean up, and then it's game over.

    Okay, now the game of walls and ladders continues. The power management folks created an escape hatch for applications which are doing things like burning a CD or controlling an industrial lathe, where shutting down the machine may not be in the user's best interest. (The user ends up with a coaster or a factory on fire.) But since they created the escape hatch, they get to control the keys to the hatch, too.

    The Shutdown­Block­Reason­Create function lets you register your application window with a custom message that is displayed to the user when they try to shut down the computer. When the danger-time is over, you call Shutdown­Block­Reason­Destroy to say that the coast is clear and shutdown is once again permitted.

    Mind you, these blocks are merely advisory. If users really want to create a coaster or burn down their factory, they can click Force shut down. One nice thing about making Windows responsible for the warning message is that if multiple applications want to block shutdown, all of them can be displayed in a single dialog, and the user only needs to click Force shut down once.

    Further guidance on system shutdown and the use of these functions can be found in the Application Shutdown Changes in Windows Vista document, which was the source material for most of this blog entry.

  • The Old New Thing

    Why don't all of my folder customizations roam with my profile?


    A customer reported some inconsistency in how folder customizations are handled by roaming profiles.

    1. Log onto Server1 with a roaming profile.
    2. Open the following folders and in each one, customize the icon size:
      1. Library\Documents
      2. \\server\share
      3. C:\Temp
    3. Log off from Server1.
    4. Log onto Server2 with the same roaming profile.
    5. Open the same folders and observe:
      1. Library\Documents: Icon size roams.
      2. \\server\share: Icon size roams.
      3. C:\Temp: Icon size does not roam.

    Why doesn't the C:\Temp customization roam?

    Well, if you think about it, it makes sense that the setting for C:\Temp doesn't roam because C:\Temp doesn't roam either! The C:\Temp on Server1 is not the same directory as the C:\Temp on Server2.

    Let's change Step 2 slightly:

    1. Log onto Server1 with a roaming profile.
    2. Open the following folders and in each one, create a file called TEST.
      1. Library\Documents
      2. \\server\share
      3. C:\Temp
    3. Log off from Server1.
    4. Log onto Server2 with the same roaming profile.
    5. Open the same folders and observe:
      1. Library\Documents: TEST is there.
      2. \\server\share: TEST is there.
      3. C:\Temp: TEST is missing.

    I think nobody would be surprised at the results of this second experiment. The changes to Library\Documents are there because that folder is part of your roaming profile. The changes to \\server\share are there because it is a global resource. And the changes to C:\Temp are not there because the first one is "C:\Temp on Server1" and the second is "C:\Temp on Server2".

    The shell saves icon size customizations in folders differently based on whether it is a global resource (like a network share) or a local resource (available only on the local machine). Settings for local resources do not roam because, well, they're local and have no meaning when roamed to another computer.

    The Documents case manages to get the desired effect, but by different means: Settings for libraries are based on how you customized the view via things like the "Arrange by" menu. Those customizations are saved in your roaming profile, and they therefore roam with you.

  • The Old New Thing

    Microsoft Dynamics in a soda can, sort of


    It is not uncommon for a product team to produce some custom soda cans (really, carbonated water) for distribution at Microsoft cafeterias. Last year, the Microsoft Dynamics™ CRM 2011 team put some custom cans of sparkling water in the coolers extoling the virtues of their new product. It took the form of a side-by-side feature comparison.

    Dynamics CRM 2011
    Used by leading companies worldwide
    Available in the cloud in at least 40 markets
    Available on premises worldwide
    Consumed through a native
    Microsoft Outlook client
    Is a great thirst quencher  
    Makes people more productive
    New flavor coming in January 2011  
    POWER of
    Learn more at http://crm.dynamics.com/

    They didn't stop at soda cans. They also branded the napkin dispensers. (The napkin dispensers have a clear panel in which a message can be inserted.)

    Dynamics CRM 2011
    Used by leading
    companies worldwide
    Used for recording
    and sharing ideas
    Available on premises
    Available in the cloud
    in 40 markets
    Cleans up messy
    IT departments
    POWER of

    Dynamics CRM 2011

    In the cloud in 40 markets
    worldwide—January 2011

    Learn more at http://crm.dynamics.com/

    (I changed the URLs from an internal Web site to the public Microsoft Dynamics™ CRM Web site.)

  • The Old New Thing

    Fabio coming to Redmond. Also: Whey Protein


    Mark your calendars: Fabio Lanzoni, better known to the world as simply Fabio, will be at the Redmond Whole Foods Market on June 21 to promote his whey protein product. (Now made with real Fabio!) And unlike Martha, he will let you take a picture of him.

    By the way, ladies, he's available!

  • The Old New Thing

    Why do you have to wait for Windows Error Reporting to check for solutions before it restarts the application?


    Leo Davidson wonders why you have to wait for Windows Error Reporting to check for solutions before it restarts the application. Why not do the two in parallel?

    Well, for one thing, when the application restarts, it might freak out if it sees a dead copy of itself. I know for sure that I would freak out if I woke up one morning and saw my own dead body lying next to me.

    While Windows Error Reporting is checking for a solution, it still has access to the carcass of the crashed application, because it may need to refer to it in order to answer follow-up questions from the server. ("Hey, was version 3.14 of PI.DLL loaded into the process when it crashed? If so, then I may have an idea what went wrong.") And so that, if you ask it to submit the crash to Microsoft, it can grab the information it needs in order to generate the crash report.

    Now suppose you start up a new copy of the application right away. If the application is a single-instance program, it will look around for another copy of itself, and hey look, it'll find its own lifeless body in the middle of the computer version of an autopsy. It will then try to send messages to the dead program, saying, "Hey, the user wants to open document X; could you do that for me?" And it won't get a response because, well, the program is dead. It's never going to respond.

    Some programs don't even try to pass information along. They just find the existing copy of the program, and call Set­Foreground­Window on its main window, thereby switching to it. Of course, what they tried to do was switched to a crashed program.

    Even worse, what if the second copy of the program tries to extract information from the existing copy of itself? If the existing copy crashed, it's highly likely that the crash was caused by corruption in the program's internal data structures. When the second copy tries to extract the corrupted data, it may itself crash. Immediately launching the replacement program creates a very quickly-growing pile of dead programs, and your screen basically gets spammed with Windows Error Reporting dialogs faster than you can click OK.

    The crashed program has effectively launched a denial of service attack against itself.

    Before trying to start the program again, Windows makes sure that the previous copy has received a proper burial. Because few programs are prepared to see their own cadaver.

    Bonus chatter: Another common scenario is that the program crashes at startup. Automatically restarting the program would just launch another copy that immediately crashes. Again, you get into the situation where you get a dozen copies of the program launched per second, all of which immediately crash.

  • The Old New Thing

    How can I determine the underlying cause of a EXCEPTION_IN_PAGE_ERROR exception?


    A customer was using memory-mapped files and installed an exception handler to log in-page errors in the memory-mapped file region. They wanted to know how they could obtain the real disk error that resulted in the memory manager not being able to page-in the requested data.

    Finding the answer isn't that hard. A quick search for EXCEPTION_IN_PAGE_ERROR reveals that the information is provided in the Exception­Information member of the EXCEPTION_RECORD structure.


    The first element of the array contains a read-write flag that indicates the type of operation that caused the access violation. If this value is zero, the thread attempted to read the inaccessible data. If this value is 1, the thread attempted to write to an inaccessible address. If this value is 8, the thread causes a user-mode data execution prevention (DEP) violation.

    The second array element specifies the virtual address of the inaccessible data.

    The third array element specifies the underlying NTSTATUS code that resulted in the exception.

    In other words,

    if (GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR)
        DiskError = GetExceptionInformation()->
  • The Old New Thing

    Eventually the window manager simply says that enough is enough


    Many window manager operations are recursive, and eventually the window manager will simply say that enough is enough when the recursion goes too deep. We've seen this when you nest windows more than 50 levels deep or nest menus more than 25 levels deep, for example. (Note also that these limits may change in the future, so don't rely on being able to walk right up to the edge. Those values came from 32-bit Windows XP; I don't know if the limits have been dropped even further in subsequent versions of Windows, and I'm not sufficiently motivated to find out.)

    A customer had some code which installed a message hook, and they found that the message hook was not called consistently. They tracked it down to another component in their application, and that component also installed a message hook. The contact actually came from the developer who maintained the other component: Did I write my message hook incorrectly? Am I accidentally messing up other message hooks in the system?

    The developer included their hook-management code, and it didn't look obviously wrong. All code paths eventually called Call­Next­Hook­Ex, so there shouldn't be any hook-loss.

    The customer was kind enough to include a copy of their program with instructions on how to trigger the problem, and stepping through the hook code quickly revealed the source of the problem. Maybe you can see it too. Here's a stack trace when the end of the hook chain is reached:

    ChildEBP RetAddr
    0011cdc4 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011cdf8 768231eb user32!DispatchHookA+0x104 
    0011ce38 76824260 user32!CallHookWithSEH+0x21 
    0011ce6c 773e642e user32!__fnHkINLPMSG+0x71 
    0011ceb0 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011ceb4 76844787 user32!NtUserCallNextHookEx+0xc 
    0011ced8 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011cf14 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011cf48 768231eb user32!DispatchHookA+0x104 
    0011cf88 76824260 user32!CallHookWithSEH+0x21 
    0011cfbc 773e642e user32!__fnHkINLPMSG+0x71 
    0011d000 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011d004 76844787 user32!NtUserCallNextHookEx+0xc 
    0011d028 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011d064 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011d098 768231eb user32!DispatchHookA+0x104 
    0011d0d8 76824260 user32!CallHookWithSEH+0x21 
    0011d10c 773e642e user32!__fnHkINLPMSG+0x71 
    0011d150 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011d154 76844787 user32!NtUserCallNextHookEx+0xc 
    0011d178 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011d1b4 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011d1e8 768231eb user32!DispatchHookA+0x104 
    0011d228 76824260 user32!CallHookWithSEH+0x21 
    0011d25c 773e642e user32!__fnHkINLPMSG+0x71 
    0011d2a0 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011d2a4 76844787 user32!NtUserCallNextHookEx+0xc 
    0011d2c8 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011d304 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011d338 768231eb user32!DispatchHookA+0x104 
    0011d378 76824260 user32!CallHookWithSEH+0x21 
    0011d3ac 773e642e user32!__fnHkINLPMSG+0x71 
    0011d3f0 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011d3f4 76844787 user32!NtUserCallNextHookEx+0xc 
    0011d418 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011d454 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011d488 768231eb user32!DispatchHookA+0x104 
    0011d4c8 76824260 user32!CallHookWithSEH+0x21 
    0011d4fc 773e642e user32!__fnHkINLPMSG+0x71 
    0011d540 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011d544 76844787 user32!NtUserCallNextHookEx+0xc 
    0011d568 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011d5a4 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011d5d8 768231eb user32!DispatchHookA+0x104 
    0011d618 76824260 user32!CallHookWithSEH+0x21 
    0011d64c 773e642e user32!__fnHkINLPMSG+0x71 
    0011d690 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011d694 76844787 user32!NtUserCallNextHookEx+0xc 
    0011d6b8 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011d6f4 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011d728 768231eb user32!DispatchHookA+0x104 
    0011d768 76824260 user32!CallHookWithSEH+0x21 
    0011d79c 773e642e user32!__fnHkINLPMSG+0x71 
    0011d7e0 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011d7e4 76844787 user32!NtUserCallNextHookEx+0xc 
    0011d808 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011d844 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011d878 768231eb user32!DispatchHookA+0x104 
    0011d8b8 76824260 user32!CallHookWithSEH+0x21 
    0011d8ec 773e642e user32!__fnHkINLPMSG+0x71 
    0011d930 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011d934 76844787 user32!NtUserCallNextHookEx+0xc 
    0011d958 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011d994 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011d9c8 768231eb user32!DispatchHookA+0x104 
    0011da08 76824260 user32!CallHookWithSEH+0x21 
    0011da3c 773e642e user32!__fnHkINLPMSG+0x71 
    0011da80 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011da84 76844787 user32!NtUserCallNextHookEx+0xc 
    0011daa8 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011dae4 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011db18 768231eb user32!DispatchHookA+0x104 
    0011db58 76824260 user32!CallHookWithSEH+0x21 
    0011db8c 773e642e user32!__fnHkINLPMSG+0x71 
    0011dbd0 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011dbd4 76844787 user32!NtUserCallNextHookEx+0xc 
    0011dbf8 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011dc34 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011dc68 768231eb user32!DispatchHookA+0x104 
    0011dca8 76824260 user32!CallHookWithSEH+0x21 
    0011dcdc 773e642e user32!__fnHkINLPMSG+0x71 
    0011dd20 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011dd24 76844787 user32!NtUserCallNextHookEx+0xc 
    0011dd48 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011dd84 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011ddb8 768231eb user32!DispatchHookA+0x104 
    0011ddf8 76824260 user32!CallHookWithSEH+0x21 
    0011de2c 773e642e user32!__fnHkINLPMSG+0x71 
    0011de70 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011de74 76844787 user32!NtUserCallNextHookEx+0xc 
    0011de98 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011ded4 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011df08 768231eb user32!DispatchHookA+0x104 
    0011df48 76824260 user32!CallHookWithSEH+0x21 
    0011df7c 773e642e user32!__fnHkINLPMSG+0x71 
    0011dfc0 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011dfc4 76844787 user32!NtUserCallNextHookEx+0xc 
    0011dfe8 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011e024 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011e058 768231eb user32!DispatchHookA+0x104 
    0011e098 76824260 user32!CallHookWithSEH+0x21 
    0011e0cc 773e642e user32!__fnHkINLPMSG+0x71 
    0011e110 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011e114 76844787 user32!NtUserCallNextHookEx+0xc 
    0011e138 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011e174 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011e1a8 768231eb user32!DispatchHookA+0x104 
    0011e1e8 76824260 user32!CallHookWithSEH+0x21 
    0011e21c 773e642e user32!__fnHkINLPMSG+0x71 
    0011e260 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011e264 76844787 user32!NtUserCallNextHookEx+0xc 
    0011e288 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011e2c4 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011e2f8 768231eb user32!DispatchHookA+0x104 
    0011e338 76824260 user32!CallHookWithSEH+0x21 
    0011e36c 773e642e user32!__fnHkINLPMSG+0x71 
    0011e3b0 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011e3b4 76844787 user32!NtUserCallNextHookEx+0xc 
    0011e3d8 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011e414 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011e448 768231eb user32!DispatchHookA+0x104 
    0011e488 76824260 user32!CallHookWithSEH+0x21 
    0011e4bc 773e642e user32!__fnHkINLPMSG+0x71 
    0011e500 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011e504 76844787 user32!NtUserCallNextHookEx+0xc 
    0011e528 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011e564 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011e598 768231eb user32!DispatchHookA+0x104 
    0011e5d8 76824260 user32!CallHookWithSEH+0x21 
    0011e60c 773e642e user32!__fnHkINLPMSG+0x71 
    0011e650 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011e654 76844787 user32!NtUserCallNextHookEx+0xc 
    0011e678 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011e6b4 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011e6e8 768231eb user32!DispatchHookA+0x104 
    0011e728 76824260 user32!CallHookWithSEH+0x21 
    0011e75c 773e642e user32!__fnHkINLPMSG+0x71 
    0011e7a0 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011e7a4 76844787 user32!NtUserCallNextHookEx+0xc 
    0011e7c8 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011e804 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011e838 768231eb user32!DispatchHookA+0x104 
    0011e878 76824260 user32!CallHookWithSEH+0x21 
    0011e8ac 773e642e user32!__fnHkINLPMSG+0x71 
    0011e8f0 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011e8f4 76844787 user32!NtUserCallNextHookEx+0xc 
    0011e918 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011e954 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011e988 768231eb user32!DispatchHookA+0x104 
    0011e9c8 76824260 user32!CallHookWithSEH+0x21 
    0011e9fc 773e642e user32!__fnHkINLPMSG+0x71 
    0011ea40 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011ea44 76844787 user32!NtUserCallNextHookEx+0xc 
    0011ea68 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011eaa4 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011ead8 768231eb user32!DispatchHookA+0x104 
    0011eb18 76824260 user32!CallHookWithSEH+0x21 
    0011eb4c 773e642e user32!__fnHkINLPMSG+0x71 
    0011eb90 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011eb94 76844787 user32!NtUserCallNextHookEx+0xc 
    0011ebb8 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011ebf4 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011ec28 768231eb user32!DispatchHookA+0x104 
    0011ec68 76824260 user32!CallHookWithSEH+0x21 
    0011ec9c 773e642e user32!__fnHkINLPMSG+0x71 
    0011ece0 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011ece4 76844787 user32!NtUserCallNextHookEx+0xc 
    0011ed08 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011ed44 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011ed78 768231eb user32!DispatchHookA+0x104 
    0011edb8 76824260 user32!CallHookWithSEH+0x21 
    0011edec 773e642e user32!__fnHkINLPMSG+0x71 
    0011ee30 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011ee34 76844787 user32!NtUserCallNextHookEx+0xc 
    0011ee58 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011ee94 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011eec8 768231eb user32!DispatchHookA+0x104 
    0011ef08 76824260 user32!CallHookWithSEH+0x21 
    0011ef3c 773e642e user32!__fnHkINLPMSG+0x71 
    0011ef80 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011ef84 76844787 user32!NtUserCallNextHookEx+0xc 
    0011efa8 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011efe4 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011f018 768231eb user32!DispatchHookA+0x104 
    0011f058 76824260 user32!CallHookWithSEH+0x21 
    0011f08c 773e642e user32!__fnHkINLPMSG+0x71 
    0011f0d0 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011f0d4 76844787 user32!NtUserCallNextHookEx+0xc 
    0011f0f8 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011f134 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011f168 768231eb user32!DispatchHookA+0x104 
    0011f1a8 76824260 user32!CallHookWithSEH+0x21 
    0011f1dc 773e642e user32!__fnHkINLPMSG+0x71 
    0011f220 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011f224 76844787 user32!NtUserCallNextHookEx+0xc 
    0011f248 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011f284 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011f2b8 768231eb user32!DispatchHookA+0x104 
    0011f2f8 76824260 user32!CallHookWithSEH+0x21 
    0011f32c 773e642e user32!__fnHkINLPMSG+0x71 
    0011f370 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011f374 76844787 user32!NtUserCallNextHookEx+0xc 
    0011f398 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011f3d4 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011f408 768231eb user32!DispatchHookA+0x104 
    0011f448 76824260 user32!CallHookWithSEH+0x21 
    0011f47c 773e642e user32!__fnHkINLPMSG+0x71 
    0011f4c0 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011f4c4 76844787 user32!NtUserCallNextHookEx+0xc 
    0011f4e8 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011f524 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011f558 768231eb user32!DispatchHookA+0x104 
    0011f598 76824260 user32!CallHookWithSEH+0x21 
    0011f5cc 773e642e user32!__fnHkINLPMSG+0x71 
    0011f610 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011f614 76844787 user32!NtUserCallNextHookEx+0xc 
    0011f638 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011f674 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011f6a8 768231eb user32!DispatchHookA+0x104 
    0011f6e8 76824260 user32!CallHookWithSEH+0x21 
    0011f71c 773e642e user32!__fnHkINLPMSG+0x71 
    0011f760 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011f764 76844787 user32!NtUserCallNextHookEx+0xc 
    0011f788 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011f7c4 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011f7f8 768231eb user32!DispatchHookA+0x104 
    0011f838 76824260 user32!CallHookWithSEH+0x21 
    0011f86c 773e642e user32!__fnHkINLPMSG+0x71 
    0011f8b0 768447aa ntdll!KiUserCallbackDispatcher+0x2e 
    0011f8b4 76844787 user32!NtUserCallNextHookEx+0xc 
    0011f8d8 6fbdb1e0 user32!CallNextHookEx+0x71 
    0011f914 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011f948 768231eb user32!DispatchHookA+0x104 
    0011f988 76824260 user32!CallHookWithSEH+0x21 
    0011f9bc 773e642e user32!__fnHkINLPMSG+0x71 
    0011f9d0 00030000 ntdll!KiUserCallbackDispatcher+0x2e 
    0011fa28 6fbdb1e0 0x30000
    0011fa64 16846e6e contoso!ContosoWindowEventHook+0x11b 
    0011fa98 768231eb user32!DispatchHookA+0x104 
    0011fad8 76824260 user32!CallHookWithSEH+0x21 
    0011fb0c 773e642e user32!__fnHkINLPMSG+0x71 
    0011fb20 00030000 ntdll!KiUserCallbackDispatcher+0x2e 
    0011fb7c 768292a9 0x30000
    0011fba8 6b2ce010 user32!PeekMessageW+0xfb 

    As you can see, a third component in their application installed at least thirty-five hooks. After the thirty-fifth hook, the window manager stepped in and said, "That's it, I'm cutting you off."

    Now, the limit isn't actually thirty-five. The window manager keeps dispatching hooks until the kernel stack starts running low, and then it gives up. This happens with a lot of recursive algorithms: The window manager plays the game for a while, but when it looks like you're about to bluescreen, it stops short and says, "Okay, I'm not going to do that any more."

    The developers now got to take their problem to the developer responsible for the Contoso component, and figure out why it's installing so many hooks. Maybe that component could try to consolidate identical hooks. Or maybe it's a leak. They never did report back (not that I was expecting them to).

    Bonus chatter: Why is hook dispatch done recursively? Shouldn't it be done iteratively?

    Remember that windows hooks came from 16-bit Windows, where economy was paramount. And the existing Call­Next­Hook pattern was preserved, though it changed to Call­Next­Hook­Ex, where you pass the hook handle directly instead of its address. One advantage of the Call­Next­Hook­Ex model over an iterative model is that explicitly forwarding to the previous hook lets you do work on the back end. I.e., you can forward the call down the chain, and then do something when control returns. This is the sort of thing you probably use a lot when you subclass a window or override a method in a derived class and call the base class from your override.

Page 2 of 3 (25 items) 123