• The Old New Thing

    The resource compiler will helpfully add window styles for you, but if you're building a dialog template yourself, you don't get that help

    • 6 Comments

    A customer was having trouble with nested dialogs. They were doing something very similar to a property sheet, with a main frame dialog, and then a collection of child dialogs that take turns appearing inside the frame dialog based on what the user is doing. The customer found that if they created the child dialogs with the Create­Dialog­Param function, everything worked great, but if they built the template at run-time, keyboard navigation wasn't working right. Specifically, one of their child dialogs contained an edit control, and while you could put focus on it with the mouse, it was not possible to tab to the control. On the other hand, a resource template didn't have this problem. Tabbing in and out worked just fine.

    There is logically no difference between a resource-based dialog template and a memory-based one, because the resource-based one is implemented in terms of the memory-based one.

    The real problem is the memory-based template you created differs somehow from the one the resource compiler generated.

    One way to identify this discrepancy is simply to do a memcmp of the two dialog templates, the resource-based one and the memory-based one, and see where they differ. After all, if you want to know why your template doesn't match the one generated by the resource compiler, you can just ask the resource compiler to generate the template and then compare the two versions.

    Instead of explaining this, I decided to invoke my psychic powers.

    My psychic powers tell me that you neglected to provide the WS_TAB­STOP style to the edit control when you created your in-memory template. (You probably didn't realize that you needed to do this, because the resource compile adds that style by default.)

    When you use the resource compiler to generate a dialog template, it sets a bunch of styles by default, depending on the type of control. For example, EDIT­TEXT says "If you do not specify a style, the default style is ES_LEFT | WS_BORDER | WS_TABSTOP."

    Not mentioned is that the default style is in addition to the defaults for all controls: WS_CHILD | WS_VISIBLE.

    If you want to turn off one of the default styles for a control, you do so with the NOT keyword. For example, if you write

       EDITTEXT IDC_AWESOME, 10, 10, 100, 100, ES_MULTILINE | NOT WS_VISIBLE
    

    the resource compiler starts with the default style of

    dwStyle = WS_CHILD | WS_VISIBLE | ES_LEFT | WS_BORDER | WS_TABSTOP;
    

    then it adds ES_MULTI­LINE:

    dwStyle |= ES_MULTILINE;
    // dwStyle value is now
    // WS_CHILD | WS_VISIBLE | ES_LEFT | WS_BORDER | WS_TABSTOP | ES_MULTILINE
    

    and then it removes WS_VISIBLE:

    dwStyle &= ~WS_VISIBLE;
    // dwStyle value is now
    // WS_CHILD | ES_LEFT | WS_BORDER | WS_TABSTOP | ES_MULTILINE
    

    which is the final style applied to the control.

    The resource compiler is trying to help you out by pre-setting the styles that you probably want, but if you don't realize that those defaults are in place, you may not realize that you need to provide them yourself when you don't use the resource compiler. Maybe it was being too helpful and ended up resulting in helplessness.

    The customer was kind enough to write back.

    Thanks! That did the trick.

    For completeness, the default dialog style is WS_POPUP­WINDOW = WS_POPUP | WS_BORDER | WS_SYS­MENU. If you have a custom font, then you also get DS_SET­FONT, and if you have a caption, then you get WS_CAPTION.

  • The Old New Thing

    When you synthesize input with SendInput, you are also synthesizing the timestamp

    • 6 Comments

    A customer was reporting a problem when they used the Send­Input function to simulate a drag/drop operation for automated testing purposes.

    I see the mouse move from one location to another, and the starting and stopping locations are correct on the screen, but the mouse moves instantaneously rather than waiting 500ms between operations. Here's how I'm sending the input.

    INPUT input[3] = { 0 };
    
    // Click
    input[0].type = INPUT_MOUSE;
    input[0].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
    input[0].mi.time = 500;
    
    // Drag
    input[1].type = INPUT_MOUSE;
    input[1].mi.dwFlags = MOUSEEVENTF_MOVE;
    input[1].mi.dx = 100;
    input[1].mi.dy = 100;
    input[1].mi.time = 1000;
    
    // Release
    input[2].type = INPUT_MOUSE;
    input[2].mi.dwFlags = MOUSEEVENTF_LEFTUP;
    input[2].mi.time = 500;
    
    SendInput(3, input, sizeof(INPUT));
    

    Well, yeah, all the events occur immediately because you submitted them all at once.

    The time field in the MOUSE­INPUT structure is not for introducing delays in playback. Though I'm not sure what the customer thought the time field was. They say that they want a 500ms delay between operations. At first, I thought that they may have misinterpreted it as a delay relative to the time the Send­Input call is made, since they set input[0].mi.time to 500 and input[1].mi.time to 1000. But if thay were the case, then setting input[2].mi.time to 500 would end up going backward in time. But looking at the big picture, it's probably not worth trying to figure out what they were thinking, since that code will have to be scrapped anyway.

    The time field is for letting an input source (typically a hardware device) say, "Hi, um, the mouse left button went down at 9:30 this morning. Yes, I know it's already 10am. The PCI bus got a flat tire, and then the spare was also flat, and really there's no point going into the details. Sorry this message arrived late." The window manager (and anybody else who bothers to check the time member of the MSG structure) uses this information to do things like detect double-clicks. If the input source later reports, "Hi, um, the mouse left button went up at 9:30:00.100 this morning, sorry for the late report," the window manager says, "Well, that was only 100 milliseconds after the button went down thirty minutes ago, so I guess that's a double-click after all. Could you try to be a bit more prompt with this information in the future?" (Sarcasm added.)

    In other words, the time member of the MOUSE­INPUT structure is for backdating input events. They still get delivered immediately, but the timestamp allows the window manager (and other code which looks at the timestamp) to make decisions about how they should respond.

    Note that post-dating the timestamp does not cause the input delivery to be delayed, and back-dating the timestamp does not cause the input to be inserted into the input stream ahead of other input. The input is merely delivered with a timestamp in the future or in the past. (And who knows what sort of havoc that will create if a program checks the timestamps and notices that they are either from the future or have traveled back in time. Maybe you'll get a call from Microsoft Research asking for more information about your time machine.)

    If you want three input events to take place with a 500ms delay between them, then you need to call Send­Input three times, with a 500ms delay between the calls.

  • The Old New Thing

    IShellFolder::BindToObject is a high-traffic method; don't do any heavy lifting

    • 6 Comments

    A customer observed that once the user visited their shell extension, Explorer ran really slowly. (Actually, it took a while just to get to this point, but I'll skip the early investigations because they aren't relevant to the story.) Some investigation showed that Explorer's tree view was calling into the shell extension, which was in turn hanging the shell for several seconds at a time.

    Explorer was calling into the shell extension because the node was in the folder tree view, and Explorer was doing a little bookkeeping to synchronize the folder state with the view. The node referred to a server that was no longer available, so when Explorer asked the shell extension, "Hey, do you have any translucent froodads for me?" the shell extension went off and tried to contact the server (30 second timeout) before returning with the answer, "Um, sorry, I'm not sure what you're talking about."

    The problem was in the shell extension's IShell­Folder::Bind­To­Object method. The Bind­To­Object method is how you navigate from a parent to a child object, but this is supposed to be a lightweight navigation. Don't try to validate that the child still exists. Just bind to the child as if it existed. Only when the client tries to do something interesting should you go check whether the object actually exists.

    You can see this in the shell, for example. Suppose you generate a pidl to a network server. Meanwhile, the network server goes down. If you try to bind to that pidl, the bind will succeeed (and quickly). Only if you then ask a question like Enum­Objects will you find out, "Oh, wait, this server doesn't actually exist."

    (Validating existence on bind doesn't really buy you much anyway, because the server might go down after the bind succeeds but before the Enum­Objects call, so clients have to be prepared anyway for the possibility of a successful bind but a failed enumeration.)

    In the shell, binding is a common operation since it's a prerequisite for talking about objects. As long as the pidl is valid, you should succeed the bind. Try not to hit the disk and definitely don't hit the network. There's plenty of time to do that later. Because the bind may not even have been done with the intention of communicating with the target; the client may have bound to the pidl just to be able to talk about the target. (As in this case, where the shell wasn't interested in the target per se; it just wanted to know if the target had a translucent froodad.)

  • The Old New Thing

    Fabio coming to Redmond. Also: Whey Protein

    • 6 Comments

    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

    How to view the stack of threads that were terminated as part of process teardown from the kernel debugger

    • 6 Comments

    As we saw some time ago, process shutdown is a multi-phase affair. After you call ExitProcess, all the threads are forcibly terminated. After that's done, each DLL is sent a DLL_PROCESS_DETACH notification. You may be debugging a problem with DLL_PROCESS_DETACH handling that suggests that some of those threads were not cleaned up properly. For example, you might assert that a reference count is zero, and you find during process shutdown that this assertion sometimes fires. Maybe you terminated a thread before it got a chance to release its reference? How can you test this theory if the thread is already gone?

    It so happens that when all the threads are terminated during the early phase of process shutdown, the kernel is a bit lazy and doesn't free their stacks. It figures, hey, the entire process is going away soon, so the stack memory is going to be cleaned up as part of process termination. (It's sort of the kernel equivalent of not bothering to sweep the floor of a building that's about to be demolished.) You can use this to your advantage by grovelling the stacks that were left behind.

    Hey, this is why you get called in to debug the hard stuff, right?

    Before continuing, I need to emphasize that this information is for debugging purposes only. The structures and offsets are all implementation details which can change from release to release.

    The first step is to identify where all the stacks are. The direct approach is difficult because the stacks can be all different sizes, so it's not easy to pick them out of a line-up. But one thing does come in a consistent size: The TEB.

    From the kernel debugger, use the !process command to dump the process you are interested in, and from the header information, extract the VadRoot.

    1: kd> !process -1
    PROCESS 8731bd40  SessionId: 1  Cid: 0748    Peb: 7ffda000  ParentCid: 0620
        DirBase: 4247b000  ObjectTable: 96f66de0  HandleCount: 104.
        Image: oopsie.exe
        VadRoot 893de570 Vads 124 Clone 0 Private 518. Modified 643. Locked 0.
        DeviceMap 995628c0
    

    Dump this VAD root with the !vad command, and pay attention only to the entries which say 1 Private READWRITE.

    1: kd> !vad 893de570
    VAD     level      start      end    commit
    ... ignore everything except "1 Private READWRITE" ...
    8730a5f0 ( 6)         50       50         1 Private      READWRITE
    9ab0cb40 ( 5)         60       7f         1 Private      READWRITE
    893978b0 ( 6)         80       9f         1 Private      READWRITE
    87302d30 ( 5)        110      110         1 Private      READWRITE
    889693f8 ( 6)        120      121         1 Private      READWRITE
    872f3fb8 ( 6)        170      170         1 Private      READWRITE
    87089a80 ( 6)        1a0      1a0         1 Private      READWRITE
    8cbf1cb0 ( 5)        1c0      1df         1 Private      READWRITE
    88c079d0 ( 6)        1e0      1e0         1 Private      READWRITE
    9abc33e0 ( 6)        410      48f         1 Private      READWRITE
    873173b0 ( 7)        970      970         1 Private      READWRITE
    8ca1c158 ( 7)      7ffd5    7ffd5         1 Private      READWRITE
    88c02a78 ( 6)      7ffd6    7ffd6         1 Private      READWRITE
    872f9298 ( 5)      7ffd7    7ffd7         1 Private      READWRITE
    8750d210 ( 7)      7ffd8    7ffd8         1 Private      READWRITE
    87075ce8 ( 6)      7ffda    7ffda         1 Private      READWRITE
    87215da0 ( 4)      7ffdc    7ffdc         1 Private      READWRITE
    872f2200 ( 6)      7ffdd    7ffdd         1 Private      READWRITE
    8730a670 ( 5)      7ffdf    7ffdf         1 Private      READWRITE
    

    (If you are debugging from user mode, then you can use !vadump but the output format is different.)

    Each of these is a candidate TEB. In practice, TEBs tend to be allocated at the high end of memory, so the ones with a low start value are probably red herrings. Therefore, you should investigate these candidates in reverse order.

    For each candidate, take the start address and append three zeroes. (Each page on x86 is 4KB, which conveniently maps to 1000 in hex.) Dump the first seven pointers of the TEB with the dp xxxxx000 L7 command.

    1: kd> dp 7ffdf000 L7
    7ffdf000  0016fbb0 00170000 0016b000 00000000
    7ffdf010  00001e00 00000000 7ffdf000 ← hit
    

    If the TEB is valid, then the seventh pointer points back to the start of the TEB. In a valid TEB, the second and third values are the stack limits; in this case, the candidate stack lives between 0016b000 and 00170000. (As a double-check, you can verify that the upper limit of the stack, 00170000 in this case, matches up with the end of a VAD allocation in the !vad output above.)

    Now that you know where the stack is, you can dps it and look for EBP frames. (I usually start about two to four pages below the upper limit of the stack.) Test out each candidate EBP frame with the k= command until you find one that seems to be solid. Record this candidate stack trace in a text file for further study.

    Repeat for each candidate TEB, and you will eventually reconstruct what each thread in the process was doing at the moment it was terminated. If you're really lucky, you might even see the code that incremented the reference count but was terminated before it could release it.

    The above discussion also applies to debugging 64-bit processes. However, instead of looking for 1 Private READWRITE pages, you want to look for 2 Private READWRITE pages. As an additional wrinkle, if you are debugging ia64, then converting a page frame to a linear address is sadly not as simple as appending three zeroes. Pages on ia64 are 8KB, not 4KB, so you need to shift the value left by 25 bits: Add three zeroes and then multiply by two.

    And finally, if you are debugging a 32-bit process on x64, then you want to look for 3 Private READWRITE pages, but add 2 before appending the three zeroes. That's because the TEB for a 32-bit process on x64 is really two TEBs glued together: A 64-bit TEB followed by a 32-bit TEB.

    Note: I did not come up with this debugging technique on my own. I learned it from an even greater debugging genius.

    Next time, we'll look at debugging this issue from a user-mode debugger.

    Trivia: The informal term for these terminated-but-not-yet-completely-destroyed threads is ghost threads. The term was coined by the Exchange support team, because they often have to study server failures that require them to do this type of investigation, and they needed a cute name for it.

  • The Old New Thing

    Who sends the initial WM_UPDATEUISTATE message?

    • 6 Comments

    Last time, we looked at the confusingly-named WM_UPDATE­UI­STATE and WM_CHANGE­UI­STATE messages. But how does the whole indicator thingie get off the ground?

    The default state for a window is to show all indicators. But as a special trick, the dialog manager will send a WM_UPDATE­UI­STATE message with UIS_INITIALIZE after the dialog has been initialized, which turns off the indicators if the last input event was a mouse event. This is its way of inferring whether the dialog box was triggered by a mouse or keyboard action and setting the initial indicators accordingly. (Note that if the user checked Underline keyboard shortcuts and access keys, then the dialog manager leaves the indicators enabled regardless of the last input event.)

    That special WM_UPDATE­UI­STATE message is what gives dialog boxes the extra special feature of hiding the keyboard accelerators until you use the keyboard.

    But notice that only the dialog manager does this. If you want this behavior in your own non-dialog windows, you will need to send the message yourself.

    BOOL MyWindow::OnCreate(...)
    {
     ... create and initialize any child windows ...
    
     // initialize indicators
     BOOL fAlwaysUnderline = FALSE;
     SystemParametersInfo(SPI_GETKEYBOARDCUES, 0,
                          &fAlwaysUnderline, 0);
     if (!fAlwaysUnderline) {
      SendMessage(this->m_hwnd, WM_UPDATEUISTATE,
                  MAKEWPARAM(UIS_INITIALIZE, 0), 0);
     }
    }
    

    Exercise: Why is it important to create and initialize the child windows before sending the WM_UPDATE­UI­STATE message?

    Exercise: Why can't the window manager do this automatically after WM_CREATE returns?

    Exercise: Explain the behavior this customer observes.

    We have a dialog box with three buttons. Sometimes the dialog displays underlines for the hotkeys, and sometimes it doesn't. I know about the feature which hides keyboard accelerators by default, but that doesn't explain why the setting gets ignored sometimes. The first time I show the dialog in my program, I get the underlines, but the second and subsequent times, I do not.
  • The Old New Thing

    Creating a simple pidl: For the times you care enough to send the very fake

    • 6 Comments

    I'll assume that we all know what pidls are and how the shell namespace uses them. That's the prerequisite for today.

    A simple pidl is an item ID list that refers to a file or directory that may not actually exist. It's a way of playing "what if": "If there were a file or directory at this location, here is what I would have created to represent it." For the times you care enough to send the very fake.

    We've seen these things in action with the SHGFI_USE­FILE­ATTRIBUTES flag, which tells the SH­Get­File­Info function, "Pretend that the file/directory exists with the attributes I specified, and tell me what the icon would be, were that item to actually exist."

    Internally, the SH­Get­File­Info function creates one of these "simple pidls", and then asks the simple pidl for its icon.

    Note that a simple pidl is really a special case of a pidl created from a WIN32_FIND_DATA. When you parse a display name with a custom bind context, and the bind context has a STR_FILE_SYS_FIND_DATA bind context object, then that object is used to control the information placed into the pidl instead of getting the information from the file system.

    Here's a program that creates a simple pidl and then does a few simple things with it. (Note that the Parsing with Parameters sample covers this topic too, so if you don't like the way I did it, you can look to see how somebody else did it.)

    #define STRICT_TYPED_ITEMIDS
    #include <new>
    #include <windows.h>
    #include <ole2.h>
    #include <oleauto.h>
    #include <shlobj.h>
    #include <propkey.h>
    #include <atlbase.h>
    #include <atlalloc.h>
    
    class CFileSysBindData : public IFileSystemBindData
    {
    public:
     static HRESULT CreateInstance(
      _In_ const WIN32_FIND_DATAW *pfd,
      _In_ REFIID riid, _Outptr_ void **ppv);
    
     // *** IUnknown ***
     IFACEMETHODIMP QueryInterface(
      _In_ REFIID riid, _Outptr_ void **ppv)
     {
      *ppv = nullptr;
      HRESULT hr = E_NOINTERFACE;
      if (riid == IID_IUnknown ||
          riid == IID_IFileSystemBindData) {
       *ppv = static_cast<IFileSystemBindData *>(this);
       AddRef();
       hr = S_OK;
      }
      return hr;
     }
    
     IFACEMETHODIMP_(ULONG) AddRef()
     {
      return InterlockedIncrement(&m_cRef);
     }
    
     IFACEMETHODIMP_(ULONG) Release()
     {
      LONG cRef = InterlockedDecrement(&m_cRef);
      if (cRef == 0) delete this;
      return cRef;
     }
    
     // *** IFileSystemBindData ***
     IFACEMETHODIMP SetFindData(_In_ const WIN32_FIND_DATAW *pfd)
     {
      m_fd = *pfd;
      return S_OK;
     }
    
     IFACEMETHODIMP GetFindData(_Out_ WIN32_FIND_DATAW *pfd)
     {
      *pfd = m_fd;
      return S_OK;
     }
    
    private:
     CFileSysBindData(_In_ const WIN32_FIND_DATAW *pfd) :
      m_cRef(1)
     {
      m_fd = *pfd;
     }
    private:
     LONG m_cRef;
     WIN32_FIND_DATAW m_fd;
    };
    
    HRESULT CFileSysBindData::CreateInstance(
     _In_ const WIN32_FIND_DATAW *pfd,
     _In_ REFIID riid, _Outptr_ void **ppv)
    {
     *ppv = nullptr;
     CComPtr<IFileSystemBindData> spfsbd;
     HRESULT hr = E_OUTOFMEMORY;
     spfsbd.Attach(new (std::nothrow) CFileSysBindData(pfd));
     if (spfsbd) {
      hr = spfsbd->QueryInterface(riid, ppv);
     }
     return hr;
    }
    

    The CFile­Sys­Bind­Data object is extraordinarily boring. It simply implements IFile­System­Bind­Data, which is a simple interface that just babysits a WIN32_FIND_DATA structure. (There is also a IFile­System­Bind­Data2 interface which babysits a little more information, but for the purpose of this program, we're interested only in the WIN32_FIND_DATA.)

    HRESULT CreateBindCtxWithOpts(
     _In_ BIND_OPTS *pbo, _Outptr_ IBindCtx **ppbc)
    {
     CComPtr<IBindCtx> spbc;
     HRESULT hr = CreateBindCtx(0, &spbc);
     if (SUCCEEDED(hr)) {
      hr = spbc->SetBindOptions(pbo);
     }
     *ppbc = SUCCEEDED(hr) ? spbc.Detach() : nullptr;
     return hr;
    }
    

    A bind context is basically a string-indexed associative array of COM objects. There is also a BIND_OPTS (or BIND_OPTS2) structure in there, but the things most people care about are the object parameters. They provide an extensible method of passing arbitrary parameters to a function. (Think of it as the COM version of the JavaScript convention of jamming random junk into an Options parameter.) You start with a IBind­Ctx parameter, and any time you need to add a new flag or parameter, you just stuff it into the IBind­Ctx. If you just want to add a new boolean flag, you can even ignore the contents of the object parameter and merely base your behavior on whether the parameter exists at all.

    HRESULT AddFileSysBindCtx(
     _In_ IBindCtx *pbc, _In_ const WIN32_FIND_DATAW *pfd)
    {
     CComPtr<IFileSystemBindData> spfsbc;
     HRESULT hr = CFileSysBindData::CreateInstance(
      pfd, IID_PPV_ARGS(&spfsbc));
     if (SUCCEEDED(hr)) {
      hr = pbc->RegisterObjectParam(STR_FILE_SYS_BIND_DATA,
                                    spfsbc);
     }
     return hr;
    }
    

    To add a file system bind parameter, you just create an object which implements IFile­System­Bind­Data and register it with the bind context with the string STR_FILE_SYS_FIND_DATA.

    HRESULT CreateFileSysBindCtx(
     _In_ const WIN32_FIND_DATAW *pfd, _Outptr_ IBindCtx **ppbc)
    {
     CComPtr<IBindCtx> spbc;
     BIND_OPTS bo = { sizeof(bo), 0, STGM_CREATE, 0 };
     HRESULT hr = CreateBindCtxWithOpts(&bo, &spbc);
     if (SUCCEEDED(hr)) {
      hr = AddFileSysBindCtx(spbc, pfd);
     }
     *ppbc = SUCCEEDED(hr) ? spbc.Detach() : nullptr;
     return hr;
    }
    

    The Create­File­Sys­Bind­Ctx function simply combines the two steps of creating a bind context and then adding a file system bind parameter to it. In casual conversation, a bind context is often named after the parameter inside it. In this case, we have a bind context with a file system bind parameter, so we call it a "file system bind context".

    HRESULT CreateSimplePidl(
     _In_ const WIN32_FIND_DATAW *pfd,
     _In_ PCWSTR pszPath, _Outptr_ PIDLIST_ABSOLUTE *ppidl)
    {
     *ppidl = nullptr;
     CComPtr<IBindCtx> spbc;
     HRESULT hr = CreateFileSysBindCtx(pfd, &spbc);
     if (SUCCEEDED(hr)) {
      hr = SHParseDisplayName(pszPath, spbc, ppidl, 0, nullptr);
     }
     return hr;
    }
    

    This is where everything comes together. To create a simple pidl, we take the WIN32_FIND_DATAW containing the metadata we want to use, put it inside a file system bind context, then use that bind context to parse the file name. The presence of a file system bind context tells the parser, "Trust me on this, just go with what's in the bind context." It suppresses all disk access, and the final pidl will describe an item that exactly matches the metadata you provided, whether that accurately reflects reality or not. (You can also pass the bind context to SHCreate­Item­From­Parsing­Name if you prefer to get an IShell­Item.)

    Okay, let's take this out for a spin.

    void DoStuffWith(_In_ PCIDLIST_ABSOLUTE pidl)
    {
     // Print the file name
     wchar_t szBuf[MAX_PATH];
     if (SHGetPathFromIDListW(pidl, szBuf)) {
      wprintf(L"Path is \"%ls\"\n", szBuf);
     }
    
     // Print the file size
     CComPtr<IShellFolder2> spsf;
     PCUITEMID_CHILD pidlChild;
     if (SUCCEEDED(SHBindToParent(pidl,
                            IID_PPV_ARGS(&spsf), &pidlChild))) {
      CComVariant vt;
      if (SUCCEEDED(spsf->GetDetailsEx(pidlChild,
                            &PKEY_Size, &vt))) {
       if (SUCCEEDED(vt.ChangeType(VT_UI8))) {
        wprintf(L"Size is %I64u\n", vt.ullVal);
       }
      }
     }
    }
    
    int __cdecl wmain(int argc, PWSTR argv[])
    {
     CCoInitialize init;
     if (SUCCEEDED(init)) {
      WIN32_FIND_DATAW fd = {};
      fd.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
      fd.nFileSizeLow = 42;
      CComHeapPtr<ITEMIDLIST_ABSOLUTE> spidlSimple;
      if (SUCCEEDED(CreateSimplePidl(&fd,
                    L"Q:\\Whatever.txt", &spidlSimple))) {
       DoStuffWith(spidlSimple);
      }
     }
     return 0;
    }
    

    Our test program asks for a simple pidl to Q:\Whatever.txt, and then prints information from it. Observe that the creation of the simple pidl succeeds even though you probably don't have a Q: drive, and even if you did, the code never tried to access it. And when we ask the pidl, "Hey, what's the file size?" it retrieves the fake value 42 we passed in the WIN32_FIND_DATAW structure.

    Sure, that was kind of artificial, but so-called simple pidls are handy if you want to talk about an object on slow media (such as a network share) without actually accessing the target device.

    Exercise: What changes are necessary in order to create a simple pidl that refers to a file with illegal characters in its name? Hint: STR_NO_VALIDATE_FILENAME_CHARS.

  • The Old New Thing

    How can I tell that I have a shell folder that represents My Computer?

    • 6 Comments

    You have in your hands an IShell­Folder, and you want to know whether this is an IShell­Folder that represents My Computer. There are a few ideas that may occur to you.

    One is to ask the folder for its current location and compare it to CSIDL_DRIVES.

    #define STRICT_TYPED_ITEMIDS
    #include <shlobj.h>
    
    PIDLIST_ABSOLUTE GetIDListViaPersistFolder(IUnknown *punk)
    {
      PIDLIST_ABSOLUTE pidl = NULL;
      IPersistFolder2 *ppf;
      if (SUCCEEDED(punk->QueryInterface(IID_PPV_ARGS(&ppf)))) {
        ppf->GetCurFolder(&pidl);
        ppf->Release();
      }
      return pidl;
    }
    
    HRESULT CompareAbsoluteIDLists(
        LPARAM lParam,
        PCUIDLIST_ABSOLUTE pidl1,
        PCUIDLIST_ABSOLUTE pidl2,
        int *piResult)
    {
      *piResult = 0;
      IShellFolder *psfDesktop;
      HRESULT hr = SHGetDesktopFolder(&psfDesktop);
      if (SUCCEEDED(hr)) {
        hr = psfDesktop->CompareIDs(lParam,
        reinterpret_cast<PCUIDLIST_RELATIVE>(pidl1),
        reinterpret_cast<PCUIDLIST_RELATIVE>(pidl2));
        if (SUCCEEDED(hr)) {
         *piResult = ShortFromResult(hr);
        }
        psfDesktop->Release();
      }
      return hr;
    }
    
    BOOL IsMyComputerFolder(IUnknown *punk)
    {
      BOOL fIsMyComputer = FALSE;
      PIDLIST_ABSOLUTE pidl = GetIDListViaPersistFolder(punk);
      if (pidl) {
        PIDLIST_ABSOLUTE pidlMyComputer;
        if (SUCCEEDED(SHGetSpecialFolderLocation(NULL,
                                     CSIDL_DRIVES, &pidlMyComputer)))
        {
          int iCompare;
          fIsMyComputer = SUCCEEDED(CompareAbsoluteIDLists(
                                   SHCIDS_CANONICALONLY,
                                   pidl, pidlMyComputer, &iCompare) &&
                          iCompare == 0;
          CoTaskMemFree(pidlMyComputer);
        }
        CoTaskMemFree(pidl);
      }
      return fIsMyComputer;
    }
    

    Okay, we have a lot of moving parts here. Let's look at them one at a time.

    The Get­IDList­Via­Persist­Folder function takes an object and asks IPersist­Folder2::Get­Cur­Folder what folder it represents. Since we don't actually use any methods on the object beyond what is provided by IUnknown, we weaken the parameter requirement to simply IUnknown.

    The Compare­Absolute­IDLists function compares two absolute ID lists according to the criteria specified by the lParam.

    The Is­My­Computer­Folder combines these two function: It takes the object you pass in and gets the ID list it represents. It then gets the ID list for the My Computer folder. And then it compares the two via SHCIDS_CANONICAL­ONLY, which means "I just want to see if they represent the same object. Don't worry about getting the sort order absolutely right." And again, since we don't use any methods on the object other than IUnknown::Query­Interface, we weaken the parameter requirements to simply IUnknown.

    Now, this code could be simplified or at least tweaked to take advantage of IShell­Item. For example, we could use SHGet­Known­Folder­Item to get the FOLDERID_Computer­Folder and then use IShell­Item::Compare.

    But I'm not going to bother, because there is an underlying algorithmic problem with this technique: It is checking whether you have a folder to My Computer specifically at its default location. If somebody creates a My Computer folder at a custom location, say via a folder shortcut, or a folder with a magic name, then this code will not recognize it as My Computer because these alternate locations for My Computer will not match the standard location.

    If you want to identify My Computer no matter where it winds up, then instead of checking the path, you can check its class.

    HRESULT GetObjectCLSID(IUnknown *punk, CLSID *pclsid)
    {
      *pclsid = CLSID_NULL;
      IPersist *pp;
      HRESULT hr = punk->QueryInterface(IID_PPV_ARGS(&pp));
      if (SUCCEEDED(hr)) {
        hr = pp->GetClassID(pclsid);
        pp->Release();
      }
      return hr;
    }
    
    BOOL IsMyComputerFolder(IUnknown *punk)
    {
      CLSID clsid;
      GetObjectCLSID(psf, &clsid);
      return clsid == CLSID_MyComputer;
    }
    

    We ask the object directly, "Hey, what's your CLSID?" and if it replies, "I am CLSID_My­Computer," then we say, "Pleased to meet you, My Computer."

  • The Old New Thing

    The relationship between module resources and resource-derived objects in 16-bit Windows

    • 6 Comments

    As we saw last time, in 16-bit Windows, resources attached to an EXE or DLL file (which I called module resources for lack of a better term) were recorded in memory as discardable global memory blocks, and the window manager accessed them directly as needed. For example, if you had an icon or a cursor, the HICON or HCURSOR was really a resource handle, and when the window manager needed to draw the icon or cursor, it would cast the icon or cursor handle to a global handle (since that's what it was under the hood), then call Lock­Resource to access the raw resource data in order to copy the pixels onto the screen.

    Similarly, accelerator tables were simply locked and accessed directly.

    On the other hand, some resources were actually templates for other objects. As suggested by their names, dialog and menu templates were just the blueprints for creating a dialog or menu. When you called Create­Dialog or Load­Menu, the template was read from memory, and a fresh new dialog or menu was created based on the template. Once that was done, the template was no longer used. You could modify the resulting dialog or menu all you want, and you were also on the hook for making sure it is destroyed. (Either by destroying it yourself or by transferring that obligation to somebody else.)

    Bitmap resources worked the same way. The resource data is a template for a new bitmap, and each time you called Load­Bitmap (or one of its moral equivalents), a brand new bitmap was created using the resource as a template. Once that was done, the template was no longer used, and you could modify the copy to your heart's content. (And you were also responsible for destroying it when you were done.)

    String resources were typically copied out of the resource section, either by the Load­String function or explicitly by your custom string extractor. The lifetime of the copied string was therefore controlled by you, and you could modify the copied string all you like since it was just a copy.

    If your custom string extractor simply returned a direct pointer to the resource rather than copying, then the pointer became invalid when the module was unloaded.

    Okay, let's summarize in a table:

    16-bit Resources
    Resource type Operation Result
    Icon Load­Icon, etc. Reference
    Cursor Load­Cursor, etc. Reference
    Accelerator Load­Accelerator, etc. Reference
    Dialog Create­Dialog, etc. Copy
    Menu Load­Menu, etc. Copy
    Bitmap Load­Bitmap, etc. Copy
    String Load­String Copy
    String Find­Resource Reference

    Some of these rules changed in the conversion from 16-bit Windows to 32-bit Windows, but in a way that tried to preserve the semantics of the operations. We'll look at those changes next time.

    But even before you get to that article, you have enough information to answer this customer's question:

    How do I recover the dialog ID from a dialog if I have the dialog's window handle?

    This is like asking, "How do I recover the recipe book that a particular cake was made from?" The cake does not know what recipe book it was made from. You might be able to do a chemical analysis followed by a thorough survey of all cookbooks in existence to try to find a match, but even if you do, it's merely a best-guess. (And if the dialog was modified after being created, then you will never find a match. Just like you will never find a cake recipe match if somebody decided to modify the cake after it came out of the oven.)

  • The Old New Thing

    Finding a printer, and then creating a shortcut to that printer

    • 6 Comments

    Today's "Little Program" does two things: It looks for a printer in the Printers folder, and then once it finds it, it creates a shortcut to that printer.

    As is common with "Little Programs", I don't bother with error checking. I'll leave you to do that.

    Second part first, since it is handy on its own: Creating a shortcut to an arbitrary item in the shell namespace, provided either in the form of an ID list or a shell item. (The ID list is the thing that identifies an item in the shell namespace.)

    void CreateShortcutToIDList(PCWSTR pszName, PCUIDLIST_ABSOLUTE pidl)
    {
     CComPtr<IShellLink> spsl;
     spsl.CoCreateInstance(CLSID_ShellLink);
     spsl->SetIDList(pidl);
     CComQIPtr<IPersistFile>(spsl)->Save(pszName, TRUE);
    }
    
    void CreateShortcutToItem(PCWSTR pszName, IShellItem *pitem)
    {
     CComHeapPtr<ITEMIDLIST_ABSOLUTE> spidl;
     CComQIPtr<IPersistIDList>(pitem)->GetIDList(&spidl);
     CreateShortcutToIDList(pszName, spidl);
    }
    

    Neither of these is particular complicated. To create a shortcut given an ID list:

    • Create a brand new Shell­Link object.
    • Tell that shell link object to point to our desired ID list.
    • Save the shell link.

    To create a shortcut given a shell item:

    • Ask the IShell­Item for its ID list.
    • Create a shortcut to that ID list.

    Okay, now the first half: Finding the printer. That is a matter of binding to the Printers folder and enumerating its contents. When we find the one whose name matches the target printer, we declare victory.

    int __cdecl wmain(int argc, wchar_t **argv)
    {
     CCoInitialize init;
     CComPtr<IShellItem> spPrinters;
     SHGetKnownFolderItem(FOLDERID_PrintersFolder, KF_FLAG_DEFAULT,
                          nullptr, IID_PPV_ARGS(&spPrinters));
     CComPtr<IEnumShellItems> spEnum;
     spPrinters->BindToHandler(nullptr, BHID_EnumItems,
                                  IID_PPV_ARGS(&spEnum));
     for (CComPtr<IShellItem> spPrinter;
          spEnum->Next(1, &spPrinter, nullptr) == S_OK;
          spPrinter.Release()) {
      CComHeapPtr<wchar_t> spszName;
      spPrinter->GetDisplayName(SIGDN_NORMALDISPLAY, &spszName);
      wprintf(L"Found printer \"%ls\"\n", spszName);
      if (lstrcmpiW(spszName, argv[1]) == 0) {
       wprintf(L"Creating shortcut as \"%ls\"\n", argv[2]);
       CreateShortcutToItem(argv[2], spPrinter);
      }
     }
     return 0;
    }
    

    This is a little trickier, but not by much.

    • Initialize COM.
    • Get the IShell­Item for the Printers folder.
    • Get the enumerator for the Printers folder.
    • For each item in the Printers folder:
      • Get its name and print it just for diagnostic purposes.
      • If the name matches the one we're looking for, then create the shortcut.

    Here are the header files I used:

    #define STRICT_TYPED_ITEMIDS // enable stricter type checking on ITEMIDs
    #include <windows.h>
    #include <atlbase.h>        // for CComPtr, CComQIPtr
    #include <atlalloc.h>       // for CComHeapPtr
    #include <shlobj.h>         // for shell interfaces
    #include <knownfolders.h>   // for FOLDERID_PrintersFolder
    #include <stdio.h>          // for wprintf
    

    For those of you who prefer to work with CSIDLs, the change is relatively minor. Replace the SHGet­Known­Folder­Item call with

     BindToCsidlItem(CSIDL_PRINTERS, &spPrinters);
    

    You may have noticed that I used the for-if anti-pattern. I could've gone for the item directly by using SHCreate­Item­From­Relative­Name:

    int __cdecl wmain(int argc, wchar_t **argv)
    {
     CCoInitialize init;
     CComPtr<IShellItem> spPrinters;
     SHGetKnownFolderItem(FOLDERID_PrintersFolder, KF_FLAG_DEFAULT,
                          nullptr, IID_PPV_ARGS(&spPrinters));
     CComPtr<IShellItem> spPrinter;
     SHCreateItemFromRelativeName(spPrinters, argv[1], nullptr,
                                  IID_PPV_ARGS(&spPrinter));
     CreateShortcutToItem(argv[2], spPrinter);
     return 0;
    }
    

    As before, if you're the sort of person who prefers to do things old-school, you can parse the name yourself, at which point you may as well give up on shell items, hike up your pants, and do it with an onion on your belt:

    HRESULT BindToIDList(PCUIDLIST_ABSOLUTE pidl,
                         REFIID riid, void **ppv)
    {
     *ppv = nullptr;
     CComPtr<IShellFolder> spsfDesktop;
     HRESULT hr = SHGetDesktopFolder(&spsfDesktop);
     if (SUCCEEDED(hr)) {
      if (pidl->mkid.cb) {
       hr = spsfDesktop->BindToObject(pidl, nullptr, riid, ppv);
      } else {
       hr = spsfDesktop->QueryInterface(riid, ppv);
      }
     }
     return hr;
    }
    
    int __cdecl wmain(int argc, wchar_t **argv)
    {
     CCoInitialize init;
     CComHeapPtr<ITEMIDLIST_ABSOLUTE> spidlPrinters;
     SHGetSpecialFolderLocation(nullptr,
                      CSIDL_PRINTERS, &spidlPrinters);
     CComPtr<IShellFolder> spsfPrinters;
     BindToIDList(spidlPrinters, IID_PPV_ARGS(&spsfPrinters));
    
     ULONG cchEaten;
     DWORD dwAttributes = 0;
     CComHeapPtr<ITEMIDLIST_RELATIVE> spidl;
     spsfPrinters->ParseDisplayName(nullptr, nullptr, argv[1],
                            &cchEaten, &spidl, &dwAttributes);
    
     CComHeapPtr<ITEMIDLIST_ABSOLUTE> spidlPrinter;
     spidlPrinter.Attach(ILCombine(spidlPrinters, spidl));
     CreateShortcutToIDList(argv[2], spidlPrinter);
     return 0;
    }
    

    The Bind­To­ID­List function is nothing special; we already saw the guts of it when we wrote Bind­To­Csidl­Item.

    The main program proceeds in three steps:

    • Get the ID list for the Printers folder and bind to it.
    • Parse the printer name, producing a printer ID list.
    • Create a shortcut to the ID list for the printers folder combined with the printer ID list.
Page 385 of 426 (4,251 items) «383384385386387»