March, 2010

  • The Old New Thing

    Why does SHFileOperation have internal error codes for DVD?

    • 12 Comments

    erangi asks why the SHFileOperation function has internal error codes DE_DEST_IS_DVD and DE_SRC_IS_DVD if DVDs hadn't been invented at the time it was first written a long time ago.

    As my colleague Christopher Davis explained, the SHFileOperation function originally came from the old File Manager of Windows 3.0, code which was written before Win32 error codes were invented. File Manager error codes and Win32 error codes had a common starting point (the MS-DOS error codes), but evolved under divergent paths. When no suitable MS-DOS error code existed for a situation that might arise during file copying, the File Manager folks invented an error code for it. Meanwhile, when no suitable MS-DOS error code existed for a situation that might arise in Win32, the Win32 folks invented an error code for it. That the two sets of error codes would come up with different meanings for the same numerical value is to be expected, since in both cases, the number was "available for use."

    Okay, so when the DVD error codes were added, why weren't they added to winerror.h to make them official instead of adding them to the internal error list?

    Well, for one, these are internal error codes (which happen to be exposed to applications in an accidental way). Why bother making official error codes for things which were meant to be internal anyway?

    "Dear kernel team, please add this error code to winerror.h."

    Okay, how should we document it?

    "Oh, it's not documented."

    Then why the heck do you need it in winerror.h?

    Second, there may be considerations that are not immediately obvious from the raw list of internal error codes. For example, I noticed that there is one error code called ERRORONDEST which is "or"d with other error codes. The case of DE_ROOTDIR | ERRORONDEST is specifically called out, but it's highly likely that the more general case applies. For example, DE_FILENAMETOOLONG | ERRORONDEST probably means that a file name on the destination was too long. If the file copy engine were to switch entirely to Win32 error codes, all the uses of internal error codes would have to broken up into two parts, one for the Win32 error code and another for the boolean that specifies whether the Win32 error code applies to the source or destination. This means changing all the functions which pass or return internal error codes, which can get particularly tricky if you were passing the error code as a parameter to SendMessage or PostMessage or some other function where you've already used up all the bits of expressiveness. (You'd have to put the information into a structure and then worry about keeping track of whose job it is to free that structure.)

    Third, there's the principle of proportionate response: Sure, the person who wanted to add DVD handling to the file copy engine could have torn apart and rewritten the way error codes are handled inside the copy engine. But would that have been a proportionate response to a request to add DVD error handling to the copy engine? "Here you go, I added DVD error handling, and I completely redesigned the way errors are handled."

    It's like asking someone to come fix a broken light switch and discovering that they rewired your house while they were there. Maybe they did a good job, or maybe they accidentally introduced a short circuit somewhere in a little-used closet. It's even more exciting when they don't even tell you that they rewired your house. You test the light switch, it works, and you thank them for a job well done. Then two months later, you visit that closet, turn on the light switch, and all the outlets on the second floor explode.

    Now, the principle of proportionate response is not a law of the universe. It's just a principle, and sometimes principles need to be broken. For example, the principle of proportionate response also results in a frog being boiled alive. Sometimes you just have to get out of the pot. But apparently this was not one of those times.

    Chris did point out that the new Vista copy engine returns HRESULTs rather than goofy internal undocumented error codes, so at least things are better now. The frog has been taken out of the pot.

    Sidebar: It was only after I had written up this message that I realized that erangi had already asked this question at the bottom of the original blog entry. If I had known that, I wouldn't have bothered writing up this entry, because I don't like it when people ask the same question in multiple places, especially since my response is based is purely speculation, guesswork that you too could have performed.

  • The Old New Thing

    How many days long is a one-day sale? The answer might surprise you

    • 20 Comments

    A friend of mine received a flyer for a major department store proudly proclaiming that they were having a One-Day-Only sale.

    Sale prices were in effect on Saturday and Sunday.

    Previously on the subject For large values of 1. If this keeps up, I may have to create a subcategory for it.

  • The Old New Thing

    How does delay-loading use binding information?

    • 10 Comments

    In the documentation for delay-loading, there's a remark that says that the call to GetProcAddress can be avoided if there is binding information. A customer who received the explanation of why you can't delay-load kernel32 pointed out that paragraph and asked whether this means that you can delay-load kernel32 if you bind to it. (Getting around to answering this question was the point of the past few days.)

    Let's take another look at what that GetProcAddress-avoidance optimization does. Actually, it's just another look at what the module loader does when it's time to resolve imports to a bound DLL: At build time, the actual function pointers are precomputed and cached, along with the timestamp of the DLL those precomputed values came from. At run time, the delay-load stubs check the timestamp of the target DLL and compare it against the timestamp that it had cached. If they are the same, then they skip the call to GetProcAddress and use the cached value.

    In other words, the delay-load stubs use binding information in exactly the same way the module loader does.

    Does this mean that you can now delay-load kernel32?

    No. First of all, if the timestamps don't match or if the target DLL was not loaded at its preferred address, then the binding information is of no use—you have a cache miss. In that case, the module loader (and the delay-load stubs) must obtain the function pointers the old-fashioned way. You can't assume that your binding information will always be accurate. (For example, after your module was bound to kernel32, there may have been a security update which modified kernel32, which invalidates your binding information.)

    And besides, even if the binding information were used, you still have to call LoadLibrary to get the target DLL loaded in the first place. Even though binding may have optimized away one call to kernel32, you still have that LoadLibrary to deal with.

  • The Old New Thing

    What is DLL import binding?

    • 25 Comments

    Last time, we saw how hinting is used to speed up the resolving of imported functions. Today, we'll look at binding.

    Recall that the module loader resolves imports by locating the function in the export table of the linked-to DLL and recording the results in the loaded module's table of imported function addresses so that code from the module can jump indirectly through the table and reach the target function.

    One of the consequences of this basic idea is that the table of imported function addresses is written to at module load time. Writeable data in a module is stored in the form of copy-on-write pages. Copy-on-write pages are a form of computer optimism: "I'm going to assume that nobody writes to this page, so that I can share it among all copies of the DLL loaded into different processes" (assuming other conditions are met, not important to this discussion; don't make me bring back the nitpicker's corner). "In this way, I can conserve memory, leaving more memory available for other things." But once you write to the page, that assumption is proven false, and the memory manager needs to make a private copy of the page for your process. If two processes load your DLL, they each get their own copy of the memory once they write to it, and the opportunity to share the memory between the two DLLs is lost.

    What is particularly sad is when the copy-on-write page is forced to be copied because two processes wrote to the pages, even if the processes wrote the same value. Since the two pages are now once again identical, they could in principle be shared again. (The memory manager doesn't do memcmps of every potentially-shared page each time you write to it, on the off chance that you happened to make two pages coincidentally identical. Once a copy-on-write page is written to, the memory manager makes the copy and says, "Oh well, it was good while it lasted.")

    One of the cases where two processes both write to the page and write the same value is when they are resolving imports to the same DLL. In that case, the call to GetProcAddress will return the same value in both processes (assuming the target DLL is loaded at the same base address in both processes), and you are in the sad case where two processes dirty the page by writing the same value.

    To make this sad case happy again, the module loader has an optimization to avoid writing to pages it doesn't have to: We pre-initialize the values in the table of imported function addresses to a prediction as to what the actual address of the function will be. Then we can have the module loader compare the return value of GetProcAddress against the predicted value, and if they are the same, it skips the write. In context diff format:

    // error checking deleted since it's not relevant to the discussion
    
    for (Index = 0; Index < NumberOfImportedFunctions; Index++) {
      FunctionPointer = GetProcAddress(hinst, ImportEntry[Index]);
    
    - TableEntry[Index] = FunctionPointer;
    
    + if (TableEntry[Index] != FunctionPointer)
    +   TableEntry[Index] = FunctionPointer;
    }
    

    But wait, we can optimize this even more. How about avoiding the entire loop? This saves us the trouble of having to call GetProcAddress in the first place.

    There is an extra field in the import descriptor table entry called TimeDateStamp which records the timestamp of the DLL from which the precomputed function pointer values were obtained. Every DLL has a timestamp, recorded in the module header information. (The format of this timestamp is in seconds since January 1, 1970, commonly known as unix time format.) Before the module loader resolves imported functions, it compares the timestamp in the import descriptor table entry against the timestamp in the actual DLL that got loaded. If they match (and if the actual DLL was loaded at its preferred base address), then the module loader skips the loop entirely: All the precomputed values are correct.

    That's the classical model for binding. There have been some changes since the original implementation, but they don't change the underlying principle: Precompute the answers and associate them with a key which lets you determine whether the information against which the values were precomputed matches the information that you actually have.

    Binding therefore is a performance optimization to address both wall-clock running time (by reducing the amount of computation performed at module load time) and memory consumption (by reducing the number of copy-on-write pages actually written to).

    Exercise: Why is the timestamp stored in the module header? Why not just use the actual file last-modified time?

    Exercise: When you rebase a DLL, does it update the timestamp?

  • The Old New Thing

    What is DLL import hinting?

    • 10 Comments

    Binding and hinting are two types of optimizations to improve the load-time performance of a module (executable or DLL). We'll start with hinting, then look at binding, and then look at how it affects delay-loading.

    The import table for a module contains a list of DLLs and a list of functions from that DLL which the module wishes to link to. The basic idea is that for each target DLL, the linker loads the DLL and then obtains the address of each imported function and from that DLL, records the results in the loaded module's table of imported function addresses.

    Hinting is a technique for speeding up this lookup.

    In addition to recording the name of the function the module wishes to link to, the linker also records the index of the function in the target DLL's export table. For example, suppose we had a DLL named FLINT whose export table looks like this:

    1Barney
    2Fred
    3Wilma

    I've numbered the entries for reasons you'll see soon.

    You wrote a DLL which imports all three of these functions. The import table for your DLL goes something like this:

    Import from FLINT
    Fred (hint = 2)
    Wilma (hint = 3)
    Barney (hint = 1)

    When your DLL gets loaded, the module loader loads the target DLL FLINT.DLL, and then it goes about resolving the imports. First up is Fred. But instead of just searching the export table of FLINT.DLL for the function Fred, it sees the hint and says, "Hey, let me look in slot 2 first." And lo and behold, there the function is in slot 2. Yay, a full search of the export table was not necessary; the hint sent us directly to the correct slot.

    The hint is just a hint, though. If FLINT.DLL doesn't have the function Fred in slot 2, then the loader just does things the old-fashioned way, by searching the export table for the Fred function.

    In general, hints are fairly resilient as long as the DLL doesn't change too much. If FLINT.DLL is updated, say by a security patch, the list of functions typically does not change, so the position in the exported names table remains unchanged. It's only if a function is added to or removed from FLINT.DLL do the hints begin to lose their effectiveness.

    Next time, we'll look at binding, which is an optimization even more powerful than hinting, but which is also more fragile.

  • The Old New Thing

    Raymond's highly scientific predictions for the 2010 NCAA men's basketball tournament

    • 14 Comments

    Once again, it's time for Raymond to come up with an absurd, arbitrary criterion for filling out his NCAA bracket.

    This year, we go to the well-known dispute arbiter Google Fight. The criterion is the number of Google hits for the quoted phrase "%s basketball", divided by the school's seed. (I would have used Bing hits, except Bing's numbers are highly erratic. Only 291 hits for "Arkansas-Pine Bluff basketball"? The results may be more meaningful, but I'm not looking for meaningful results; I'm looking for numbers I can plug into my bracket-o-matic.)

    Once the field has been narrowed to eight teams, the results are determined by a coin flip.

    Update:

    • Correct predictions are in green.
    • Incorrect predictions are in red.
    • (!) marks upsets correctly predicted.
    • (*) marks upsets predicted but did not take place.
    • (x) marks actual upsets not predicted.

    Opening Round Game

    Arkansas-Pine Bluff(80,900) Arkansas-Pine Bluff
    (80,900)
    Winthrop(29,200)

    Midwest bracket

    1Kansas(94,200) Kansas
    (94,200)
    Kansas
    (94,200) (x)
    Kansas
    (heads)
    Kansas
    16Lehigh(1,400)
    8UNLV(43,875) Northern Iowa
    (57,444) (!)
    9Northern Iowa(57,444)
    5Michigan State(15,120) Michigan State
    (15,120)
    Maryland
    (27,750) (x)
    12New Mexico State(11,083)
    4Maryland(27,750) Maryland
    (27,750)
    13Houston(6,784)
    6Tennessee(11,550) San Diego State
    (17,272) (*)
    Georgetown
    (24,766)
    Ohio State
    (tails)
    11San Diego State(17,272)
    3Georgetown(24,766) Georgetown
    (24,766) (x)
    14Ohio(2,057)
    7Oklahoma State(2,857) Oklahoma State
    (2,857) (x)
    Ohio State
    (34,700)
    10Georgia Tech(2,750)
    2Ohio State(34,700) Ohio State
    (34,700)
    15UC Santa Barbara(4,746)

    West bracket

    1Syracuse(131,000) Syracuse
    (131,000)
    Syracuse
    (131,000)
    Syracuse
    (tails)
    Pittsburgh
    16Vermont(5,312)
    8Gonzaga(5,325) Gonzaga
    (5,325)
    9Florida State(1,644)
    5Butler(26,000) Butler
    (26,000)
    Vanderbilt
    (61,750)
    12UTEP(7,600)
    4Vanderbilt(61,750) Vanderbilt
    (61,750) (x)
    13Murray State(6,761)
    6Xavier(26,500) Xavier
    (26,500)
    Pittsburgh
    (54,666) (x)
    Pittsburgh
    (heads)
    11Minnesota(3,427)
    3Pittsburgh(54,666) Pittsburgh
    (54,666)
    14Oakland(1,728)
    7BYU(6,785) BYU
    (6,785)
    Kansas State
    (25,000)
    10Florida(5,850)
    2Kansas State(25,000) Kansas State
    (25,000)
    15North Texas(7,066)

    East bracket

    1Kentucky(285,000) Kentucky
    (285,000)
    Kentucky
    (285,000)
    Kentucky
    (tails)
    New Mexico
    16East Tennessee State(3,687)
    8Texas(24,250) Texas
    (24,250) (x)
    9Wake Forest(3,022)
    5Temple(32,600) Cornell
    (60,000) (!)
    Cornell
    (60,000) (!)
    12Cornell(60,000)
    4Wisconsin(8,325) Wisconsin
    (8,325)
    13Wofford(2,192)
    6Marquette(15,283) Marquette
    (15,283) (x)
    New Mexico
    (17,633)
    New Mexico
    (heads)
    11Washington(4,009)
    3New Mexico(17,633) New Mexico
    (17,633)
    14Montana(1,778)
    7Clemson(4,885) Clemson
    (4,885) (x)
    West Virginia
    (10,800)
    10Missouri(3,700)
    2West Virginia(10,800) West Virginia
    (10,800)
    15Morgan State(5,686)

    South bracket

    1Duke(363,000) Duke
    (363,000)
    Duke
    (363,000)
    Duke
    (heads)
    Duke
    16Arkansas-Pine Bluff(5,056)
    8California(2,487) Louisville
    (17,444) (*)
    9Louisville(17,444)
    5Texas A&M(156,000) Texas A&M
    (156,000)
    Texas A&M
    (156,000) (*)
    12Utah State(0,883)
    4Purdue(12,000) Purdue
    (12,000)
    13Siena(0,900)
    6Notre Dame(14,450) Old Dominion
    (14,454) (!)
    Old Dominion
    (14,454) (*)
    Villanova
    (tails)
    11Old Dominion(14,454)
    3Baylor(11,100) Baylor
    (11,100)
    14Sam Houston State(4,671)
    7Richmond(5,714) Saint Mary's
    (9,900) (!)
    Villanova
    (18,000) (x)
    10Saint Mary's(9,900)
    2Villanova(18,000) Villanova
    (18,000)
    15Robert Morris(13,000)

    Finals

    Kansas(tails) Pittsburgh
    (heads)
    Pittsburgh
    Pittsburgh(heads)
    New Mexico(heads) New Mexico
    (tails)
    Duke(tails)
  • The Old New Thing

    Why does my control send its notifications to the wrong window after I reparent it?

    • 2 Comments

    MontagFTB noticed that some controls have the problem that if you reparent the control, it still sends notifications to its old parent. We looked at the faulty diagnosis last time. What's the real reason?

    The control cached its original parent window.

    Most complex controls communicate with the parent window frequently, and in order to avoid calling GetParent, the control gets its parent once and caches the value for future use. Under normal conditions, this cache works very well since reparenting a window is extremely rare and is generally considered an unusual condition. Like the adoption of a child, it's the sort of thing you should only be doing with the coordination of all three parties (the old parent, the new parent, and the child).

    When you reparent the control, the cached value in the child window is no longer correct. But since you didn't coordinate this with the child window, the control doesn't know this, and it keeps talking to the old parent. Unlike the Post Office, you can't submit a change of address form to the window manager and tell it, "Hey, if somebody tries to send a message to windows X, deliver it to window Y if the return address is window Z." (Actually, the Post Office stops forwarding mail after one year.)

    Since window reparenting is considered to be an unusual condition, most controls don't have provisions for telling them, "Hey, I reparented you. Please send future notifications to that window over there." The window manager is fine with all your reparenting games, but the other participants may have made assumptions based on the stability of the window hierarchy.

    Where does that leave MontagFTB? (It is at this point where a general topic gradually turns into addressing questions that are applicable only to MontagFTB's situation and aren't all that useful to others. This is something I try to avoid, because this is a blog, not a consulting service.)

    First, you can avoid the staging window and just create the controls with the correct parent. I don't know why the staging window was necessary, so this may not be a viable solution. If it was merely to avoid flicker, then you can create the controls as hidden windows, and then do a massive ShowWindow when they are ready. Or you can create the controls at negative coordinates (so they don't appear inside the parent's client rectangle), and then when you're ready, perform a big EndDeferWindowPos to move them all into position at once.

    If you really need to have the staging window, you can have the staging window do the message forwarding. If it receives a WM_COMMAND or WM_NOTIFY notification message from one of these given-away child windows, it just forwards the message to the new owner. However, this violates the guideline that "When reparenting a window, the old parent, the new parent, and the child all need to be involved in the process if you want the adoption to go smoothly," so I would not recommend it.

    If you don't want to make the staging window have to deal with message forwarding (for example, if you intend on destroying the staging window once you have removed all the child windows), then you can insert a level of redirection: Create a container window as a child of the staging window, and create the child windows as children of the container. Then when it's time to reparent the controls, move the container window to the new parent. This adheres to the guideline because the windows involved in the reparenting (the final destination, the staging window, and the container window) are all under your control, and therefore you can make sure all internal state is correct when you change the bookkeeping relationship among them. And since the controls are destined for a dialog box, you should give the container the WS_EX_CONTROLPARENT style so that they can participate in dialog box navigation.

  • The Old New Thing

    Robots and humans coexisting, can it be done peacefully?

    • 37 Comments

    Everybody who follows science fiction knows that if you have robots and humans living in the same world, eventually something bad happens to the humans

    But we're going to chance it one more time.

    Every so often, I stumble across a Web site that translates my articles into another language. I occasionally see a Japanese translation, and I think there's a Russian translation out there somewhere. In addition to those human translations, there are also robot translations available through your favorite online translation service.

    Now the two can coexist.

    There's a new widget on this page which generates a robot translation, but there's more to it than that. Microsoft Translator's new Collaborative Translations Framework lets you suggest improvements to the robot translation, thereby helping the robot do a better job in the future. Visitors can view the translation alternatives and vote for the best one, and all this information gets funneled back to the Web site administrator, who can select one of the translations to be the one that appears by default the next time somebody asks to translate the page.

    And just because I'm so cool, the translation folks have provided a team of native speakers to act as the translation administrators for my Web site. That's good for me, because I am not really qualified to rate the quality of translations in French, or Spanish, or, um, pretty much anything other than English. (My understanding of German and Swedish gets me as far as identifying what is intelligible, but I don't claim to have a grasp of the subtleties.)

    Let's hope this doesn't destroy humanity.² (But if it does, at least your participation puts you on the winning side, right?)

    Footnotes

    ¹Probably because stories that go "Humans and robots work together, but then the humans turn off the robots" aren't as compelling.

    ²I'm pretty sure the Microsoft Translator team is not too happy that I'm suggesting that their invention may destroy humanity. Hey, I'm not saying it's going to happen. But it's possible.

    Bonus chatter: If you have questions about the Collaborative Translations Framework, you can ask them on the Microsoft Translator forum.

  • The Old New Thing

    A window can have a parent or an owner but not both

    • 19 Comments

    MontagFTB had a problem which, upon investigation, allegedly was caused by a subtle "fact": "The parent specified in CreateWindowEx is both the parent of the window and the owner of the window, but when you call SetParent it only sets the parent of the window, not the owner." MontagFTB then concluded that some messages were sent to the parent and others were sent to the owner.

    This is a faulty diagnosis. We'll look at the correct diagnosis next time, but today's topic is parents and owners. Actually, parent and owner windows were already covered by my 2005 PDC talk, Five Things Every Win32 Programmer Should Know, so for most of you, today's topic is a review. (And I included the topic in the talk specifically so I wouldn't have to blog about it, but obviously that plan didn't work out.)

    A window can be created as a child window (WS_CHILD set) or a top-level window (WS_CHILD not set). A child window has a parent, which you specify when you call CreateWindowEx, and which you can change by calling SetParent. A top-level window, on the other hand, has no parent. Its parent is NULL.

    Ownership is a concept that relates top-level windows. A top-level window can optionally have an owner, which is also specified when you call CreateWindowEx, and which you can change by a complicated mechanism described in my talk.

    Note that changing a window's parent or owner is not a normal operation. You usually create a window with a specific parent or owner and leave it that way for the lifetime of the window.

    Now, a window can have a parent, or it can have an owner, or it can have neither, but it can never have both.

    What would it mean for a window to have both a parent and an owner? Well, in order to have a parent, the window must itself be a child. But in order to have an owner, the window must be top-level. And top-level windows and child windows are mutually exclusive (and collectively exhaustive), because you either have the WS_CHILD style (which makes you a child) or you don't (which makes you top-level). Since people like tables so much, here's a table:

    Child windowTop-level window
    The Parent window is...non-NULL NULL
    The Owner window is...N/A NULL or non-NULL

    The box for "The Owner window of a Child window..." is marked N/A because the question is meaningless. Ownership is a relationship among top-level windows.

    By analogy, consider the people at a school for children. They can be separated into two groups, students and teachers. (We'll treat non-teaching staff as teachers with no students.)

    Each student is assigned to a teacher. Each teacher might or might not have another teacher as a mentor. Several students can be assigned the same teacher, but every student must be assigned to some teacher. Similarly, several teachers might have the same mentor, but some teachers won't have a mentor at all, and some mentors might themselves have mentors.

    It's impossible for a person to have both a teacher and a mentor, because having a teacher applies only to students, and having a mentor applies only to teachers. Teachers don't attend classes (they lead the classes) so they don't have a teacher. But they might have mentors. Asking for a student's mentor is a meaningless question because students don't have mentors; teachers do.

    Since a window cannot have both a parent and an owner, the CreateWindowEx function takes a single HWND parameter which is either the parent or owner of the window being created, depending on what type of window you're creating. If you're creating a child window, then the parameter represents the parent window; if you're creating a top-level window, then the parameter represents the owner window.

    A similar overloading of parameters happens with the HMENU: If you're creating a child window, then the parameter represents the child window identifier; if you're creating a top-level window, then the parameter represents the window menu. Because only top-level windows can have menus, and only child windows can have child window identifiers.

    If this parameter overloading bothers you, you can write your own helper functions:

    HWND CreateChildWindowEx(
        DWORD dwExStyle,
        LPCTSTR lpClassName,
        LPCTSTR lpWindowName,
        DWORD dwStyle,
        int x,
        int y,
        int nWidth,
        int nHeight,
        HWND hWndParent,
        UINT_PTR id,
        HINSTANCE hInstance,
        LPVOID lpParam
    )
    {
     // A child window must have the WS_CHILD style
     if (!(dwStyle & WS_CHILD)) {
      SetLastError(ERROR_INVALID_PARAMETER);
      return NULL;
     }
     return CreateWindowEx(
        dwExStyle,
        lpClassName,
        lpWindowName,
        dwStyle,
        x,
        y,
        nWidth,
        nHeight,
        hWndParent,
        reinterpret_cast<HMENU>(id),
        hInstance,
        lpParam);
    }
    
    HWND CreateTopLevelWindowEx(
        DWORD dwExStyle,
        LPCTSTR lpClassName,
        LPCTSTR lpWindowName,
        DWORD dwStyle,
        int x,
        int y,
        int nWidth,
        int nHeight,
        HWND hWndOwner,
        HMENU hMenu,
        HINSTANCE hInstance,
        LPVOID lpParam
    )
    {
     // A top-level window must not have the WS_CHILD style
     if (dwStyle & WS_CHILD) {
      SetLastError(ERROR_INVALID_PARAMETER);
      return NULL;
     }
     return CreateWindowEx(
        dwExStyle,
        lpClassName,
        lpWindowName,
        dwStyle,
        x,
        y,
        nWidth,
        nHeight,
        hWndOwner,
        hMenu,
        hInstance,
        lpParam);
    }
    

    There's more to parent windows and owner windows than what I've written here; I refer you to my talk (or other documentation) for more details.

    Next time, we'll look at what MontagFTB is really seeing.

  • The Old New Thing

    Simplifying context menu extensions with IExecuteCommand

    • 14 Comments

    The IExecuteCommand interface is a simpler form of context menu extension which takes care of the annoying parts of IContextMenu so you can focus on your area of expertise, namely, doing the actual thing the user selected, and leave the shell to doing the grunt work of managing the UI part.

    I've never needed a scratch shell extension before, so I guess it's time to create one. This part is completely boring, and those of you who have written COM inproc servers can skip over it.

    #include <windows.h>
    #include <new>
    
    LONG g_cObjs;
    
    void DllAddRef() { InterlockedIncrement(&g_cObjs); }
    void DllRelease() { InterlockedDecrement(&g_cObjs); }
    
    // guts of shell extension go in here eventually
    
    class CFactory : public IClassFactory
    {
    public:
     // *** IUnknown ***
     STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
     STDMETHODIMP_(ULONG) AddRef() { return 2; }
     STDMETHODIMP_(ULONG) Release() { return 1; }
    
     // *** IClassFactory ***
     STDMETHODIMP CreateInstance(IUnknown *punkOuter,
                                 REFIID riid, void **ppv);
     STDMETHODIMP LockServer(BOOL fLock);
    };
    
    CFactory c_Factory;
    
    STDMETHODIMP CFactory::QueryInterface(REFIID riid, void **ppv)
    {
     IUnknown *punk = NULL;
     if (riid == IID_IUnknown || riid == IID_IClassFactory) {
      punk = static_cast<IClassFactory*>(this);
     }
     *ppv = punk;
     if (punk) {
      punk->AddRef();
      return S_OK;
     } else {
      return E_NOINTERFACE;
     }
    }
    
    STDMETHODIMP CFactory::CreateInstance(
     IUnknown *punkOuter, REFIID riid, void **ppv)
    {
     *ppv = NULL;
     if (punkOuter) return CLASS_E_NOAGGREGATION;
     CShellExtension *pse = new(std::nothrow) CShellExtension();
     if (!pse) return E_OUTOFMEMORY;
     HRESULT hr = pse->QueryInterface(riid, ppv);
     pse->Release();
     return hr;
    }
    
    STDMETHODIMP CFactory::LockServer(BOOL fLock)
    {
     if (fLock) DllAddRef();
     else       DllRelease();
     return S_OK;
    }
    
    STDAPI DllGetClassObject(REFCLSID rclsid,
                             REFIID riid, void **ppv)
    {
     if (rclsid == CLSID_ShellExtension) {
      return c_Factory.QueryInterface(riid, ppv);
     }
     *ppv = NULL;
     return CLASS_E_CLASSNOTAVAILABLE;
    }
    
    STDAPI DllCanUnloadNow()
    {
     return g_cObjs ? S_OK : S_FALSE;
    }
    

    I'm assuming that the above code is all old hat. Consider it a prerequisite.

    Okay, now the good stuff.

    The IExecuteCommand interface is used when you create a static registration for a shell verb but specify DelegateExecute in the command. Our sample shell extension will be active on text files, and all it'll do is print the file names to the debugger.

    Since we're a COM server, we need to register our CLSID. This should also be very familiar to you.

    [HKEY_CLASSES_ROOT\CLSID\{guid}\InProcServer32]
    @="C:\path\to\scratch.dll"
    "ThreadingModel"="Apartment"
    

    Here's where we register our object as a verb for text files, specifying that it should be invoked via DelegateExecute:

    [HKEY_CLASSES_ROOT\txtfile\shell\printnamestodebugger]
    @="Print names to debugger"
    
    [HKEY_CLASSES_ROOT\txtfile\shell\printnamestodebugger\command]
    "DelegateExecute"="{guid}"
    

    That was the easy part. Now to roll up our sleeves and write the shell extension.

    #include <shobjidl.h>
    
    CLSID CLSID_ShellExtension = { ...guid... };
    
    class CShellExtension
     : public IExecuteCommand
     , public IInitializeCommand
     , public IObjectWithSelection
    {
    public:
     CShellExtension();
    
     // *** IUnknown ***
     STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
     STDMETHODIMP_(ULONG) AddRef();
     STDMETHODIMP_(ULONG) Release();
    
     // *** IInitializeCommand ***
     STDMETHODIMP Initialize(PCWSTR pszCommandName, IPropertyBag *ppb);
    
     // *** IObjectWithSelection ***
     STDMETHODIMP SetSelection(IShellItemArray *psia);
     STDMETHODIMP GetSelection(REFIID riid, void **ppv);
    
     // *** IExecuteCommand ***
     STDMETHODIMP SetKeyState(DWORD grfKeyState) { return S_OK; }
     STDMETHODIMP SetParameters(LPCWSTR pszParameters) { return S_OK; }
     STDMETHODIMP SetPosition(POINT pt) { return S_OK; }
     STDMETHODIMP SetShowWindow(int nShow) { return S_OK; }
     STDMETHODIMP SetNoShowUI(BOOL fNoShowUI) { return S_OK; }
     STDMETHODIMP SetDirectory(LPCWSTR pszDirectory) { return S_OK; }
     STDMETHODIMP Execute();
    
    private:
     ~CShellExtension();
    private:
     LONG m_cRef;
     IShellItemArray *m_psia;
    };
    
    CShellExtension::CShellExtension()
     : m_cRef(1), m_psia(NULL)
    {
     DllAddRef();
    }
    
    CShellExtension::~CShellExtension()
    {
     if (m_psia) m_psia->Release();
     DllRelease();
    }
    

    I've written this all out longhand; I'm trusting that you're using some sort of framework (like, say, ATL) which avoids all this tedium, but since different people may choose different frameworks, I won't choose a framework here. Instead, we just have the boring IUnknown methods.

    STDMETHODIMP CShellExtension::QueryInterface(
     REFIID riid, void **ppv)
    {
     IUnknown *punk = NULL;
     if (riid == IID_IUnknown || riid == IID_IExecuteCommand) {
      punk = static_cast<IExecuteCommand*>(this);
     } else if (riid == IID_IInitializeCommand) {
      punk = static_cast<IInitializeCommand*>(this);
     } else if (riid == IID_IObjectWithSelection) {
      punk = static_cast<IObjectWithSelection*>(this);
     }
     *ppv = punk;
     if (punk) {
      punk->AddRef();
      return S_OK;
     } else {
      return E_NOINTERFACE;
     }
    }
    
    STDMETHODIMP_(ULONG) CShellExtension::AddRef()
    {
     return ++m_cRef;
    }
    
    STDMETHODIMP_(ULONG) CShellExtension::Release()
    {
     ULONG cRef = --m_cRef;
     if (cRef == 0) delete this;
     return cRef;
    }
    

    Whew. Up until now, it's just been boring typing that you have to do for any shell extension. Finally we can start doing something interesting. Windows 7 will initialize your shell extension with information about the command being executed. For this particular shell extension, we'll just print the command name to the debugger to prove that something happened. (In real life, you might use the same CShellExtension to handle multiple commands, and this lets you determine which command you're being asked to execute.)

    STDMETHODIMP CShellExtension::Initialize(
     PCWSTR pszCommandName,
     IPropertyBag *ppb)
    {
     OutputDebugStringW(L"Command: ");
     OutputDebugStringW(pszCommandName);
     OutputDebugStringW(L"\r\n");
     return S_OK;
    }
    

    The shell will give you the items on which to execute in the form of an IShellItemArray:

    STDMETHODIMP CShellExtension::SetSelection(IShellItemArray *psia)
    {
     if (psia) psia->AddRef();
     if (m_psia) m_psia->Release();
     m_psia = psia;
     return S_OK;
    }
    
    STDMETHODIMP CShellExtension::GetSelection(
     REFIID riid, void **ppv)
    {
     if (m_psia) return m_psia->QueryInterface(riid, ppv);
     *ppv = NULL;
     return E_NOINTERFACE;
    }
    

    The shell will then call a bunch of IExecuteCommand::SetThis and IExecuteCommand::SetThat methods to inform you of the environment in which you have been asked to execute. We just ignored them all for simplicity, but in practice, you may want to pay attention to some of them, particularly IExecuteCommand::SetPosition, IExecuteCommand::SetShowWindow, and IExecuteCommand::SetNoShowUI.

    After all the IExecuteCommand::SetXxx methods have been called, it's show time:

    STDMETHODIMP CShellExtension::Execute()
    {
     HRESULT hr;
     if (m_psia) {
      IEnumShellItems *pesi;
      if (SUCCEEDED(hr = m_psia->EnumItems(&pesi))) {
       IShellItem *psi;
       while (pesi->Next(1, &psi, NULL) == S_OK) {
        LPWSTR pszName;
        if (SUCCEEDED(psi->GetDisplayName(SIGDN_FILESYSPATH,
                                          &pszName))) {
         OutputDebugStringW(L"File: ");
         OutputDebugStringW(pszName);
         OutputDebugStringW(L"\r\n");
         CoTaskMemFree(pszName);
        }
        psi->Release();
       }
       pesi->Release();
       hr = S_OK;
      }
     } else {
      hr = E_UNEXPECTED;
     }
     return hr;
    }
    

    All we do is enumerate the contents of the IShellItemArray and print their file names (if they have one). Instead of IEnumShellItems, you can use IShellItemArray::GetCount and IShellItemArray::GetItemAt. Or, if you are porting an existing context menu that uses IDataObject, you can call IShellItemArray::BindToHandler(BHID_DataObject) to turn your IShellItemArray into an IDataObject.

    Install this shell extension, right-click on a text file (or a bunch of text files), and select Print names to debugger. If all goes well, the debugger will report Command: printnamestodebugger followed by paths of the files you selected.

    But wait, there's more. The IPropertyBag passed to IInitializeCommand::Initialize contains additional configuration options taken from the registry. You can use this to customize the behavior of the shell extension further. Put the bonus information under the command key like this:

    [HKEY_CLASSES_ROOT\txtfile\shell\printnamestodebugger]
    "extra"="Special"
    
    STDMETHODIMP CShellExtension::Initialize(
     PCWSTR pszCommandName,
     IPropertyBag *ppb)
    {
     OutputDebugStringW(L"Command: ");
     OutputDebugStringW(pszCommandName);
     OutputDebugStringW(L"\r\n");
     if (ppb) {
      VARIANT vt;
      VariantInit(&vt);
      if (SUCCEEDED(ppb->Read(L"extra", &vt, NULL))) {
       if (SUCCEEDED(VariantChangeType(&vt, &vt, 0, VT_BSTR))) {
        OutputDebugStringW(L"extra: ");
        OutputDebugStringW(vt.bstrVal);
        OutputDebugStringW(L"\r\n");
       }
       VariantClear(&vt);
      }
     }
     return S_OK;
    }
    

    This updated version of CShellExtension looks for that registry value extra we set above and if found prints its value to the debugger.

    Okay, so it looks like a lot of typing, but most of that was typing you have to do for any shell extension. The part that is specific to IExecuteCommand is not that bad, and it certainly avoids having to mess with IContextMenu::QueryContextMenu and the fifty bajillion variations on IContextMenu::InvokeCommand. Furthermore, the shell doesn't even load your IExecuteCommand handler until the user selects your command, so switching to a static registration also gives the system a bit of a performance boost.

    Bonus tip: You can combine the IExecuteCommand technique with Getting Dynamic Behavior for Static Verbs by Using Advanced Query Syntax and Using Item Attributes to specify the conditions under which you want your verb to appear without having to write a single line of C++ code. Choosing a Static or Dynamic Shortcut Menu Method provides additional guidance on choosing among the various methods for registering verbs.

    One nice thing about IExecuteCommand is that it supports out-of-proc activation (i.e., local server rather than in-proc server). This means that it supports cross-bitness shell extensions: If you don't have the time to port your 32-bit shell extension to 64-bit, you can register it as an out-of-proc IExecuteCommand. When running on 64-bit Windows, the 64-bit Explorer will launch your 32-bit server to handle the command. Conversely, if your IExecuteCommand is a 64-bit local server, a 32-bit application can still invoke it.

    (We'll see more about local server shell extensions in a few months. This was just foreshadowing.)

Page 2 of 4 (35 items) 1234