February, 2011

  • The Old New Thing

    What is the difference between a directory and a folder?

    • 37 Comments

    Windows 95 introduced Windows Explorer and along with it the term folder. What is the relationship between folders and directories?

    Some people believe that Windows 95 renamed directories to folders, but it's actually more than that.

    Windows Explorer lets you view folders, which are containers in the shell namespace. Directories are one type of folder, namely, folders which correspond to file system locations. There are other types of folders, such as Control Panel or Network Neighborhood or Printers. These other types of folders represent objects in the shell namespace which do not correspond to files. In common usage, the term virtual folder has been applied to refer to folders which are not directories. In other words, we have this Euler diagram:

    Folders

    Directories
    Virtual folders = Folders − Directories

    In general, code which manipulates the shell namespace should operate on folders and items, not directories and files, so as not to tie themselves to a particular storage medium. For example, code which limits itself to files won't be able to navigate into a Zip file, since the contents of a Zip file are exposed in the form of a virtual folder.

    Update: The Web server "helpfully" closed some tags prematurely. Apparently it doesn't want you to nest tables. Replaced nested table with DIV.

  • The Old New Thing

    If an operation results in messages being sent, then naturally the target window must be processing messages for the operation to complete

    • 16 Comments

    If an operation includes as part of its processing sending messages, then naturally the target window for those messages must be processing messages (or more precisely, the thread which owns the target window must be processing messages) in order for the operation to complete. Why? Because processing messages is the only way a window can receive messages!

    It's sort of tautological yet not obvious to everyone.

    Generally you run into this problem when you try to manipulate a window from a thread different from the one which created the window. Since windows have thread affinity, operations from off-thread typically need to get moved onto the thread which owns the window because that's where the window really "lives".

    The window manager will often try to see how much it can do without marshalling to the thread which owns the window, but when message traffic is involved, you are pretty much stuck. Messages are delivered to a window on the thread to which the window belongs, and there's no way around that.

    There are subtle ways in which a function called off-thread can result in message traffic. Generally speaking, you should just assume that any operation on a window may generate messages: Even if they don't do so today, they may do so in the future. For example, changing a window's style did not generate message traffic in early versions of Windows, but in Windows 95, it began generating WM_STYLECHANGING and WM_STYLECHANGED messages. This isn't called out explicitly in the documentation for SetWindowLong but it's implied by the documentation for WM_STYLECHANGING and WM_STYLECHANGED.

    Why isn't there an explicit callout in the documentation for SetWindowLong? At the time the SetWindowLong documentation was originally written, the WM_STYLECHANGING and WM_STYLECHANGED messages did not exist. Therefore the documentation was complete at the time of writing. Circumstances changed elsewhere in the system that had secondary effects on SetWindowLong, but nobody bothered to update the documentation, probably because it didn't even occur to anybody that these effects existed. And then these secondary effects lead to tertiary effects: SetScrollInfo may change the window style to add or remove the WS_HSCROLL or WS_VSCROLL style, which in turn results in a call to SetWindowLong which in turn results in sending the WM_STYLECHANGING and WM_STYLECHANGED messages. Next come quaternary effects on functions like FlatSB_SetScrollInfo, since they call SetScrollInfo as part of their functioning. And so on, and so on. Just tracking down the full ripple effect of those two new messages is probably impossible.

    But the root cause of all these ripple effects is operating on a window (particularly modifying a window) from a thread different from the thread that owns the window. Avoid that, and you'll avoid the whole issue of which operations generate messages and which manage to sneak by without needing to send any messages (at least not yet).

  • The Old New Thing

    Psychic debugging: Because of course when something doesn't work, it's because the program was sabotaged from above

    • 25 Comments

    When something stops working, you begin developing theories for why it doesn't work, and normally, you start with simple theories that involve things close to you, and only after you exhaust those possibilities do you expand your scope. Typically, you don't consider that there is a global conspiracy against you, or at least that's not usually your first theory.

    I'm trying to use the XYZ.DLL that comes with your product. I have successfully registered this DLL (as specified in the documentation) by performing a regsvr32 C:\path\to\XYZ.DLL.

    According to the documentation, I should now be able to create a Xyz.Xyz­Widgetizer object, but when I try to do so from C#, I get the exception

    Retrieving the COM class factory for component with CLSID
    {...} failed due to the following error: 80040154.
    

    I tried using the Visual Basic code sample which comes with the documentation, which contains only two lines:

    Dim oXyzWidgetizer
    Set oXyzWidgetizer = WScript.CreateObject("Xyz.XyzWidgetizer")
    

    However, it still fails with the following error:

    Microsoft (R) Windows Script Host Version 5.7
    Copyright (C) Microsoft Corporation. All rights reserved.
    C:\test.vbs(2, 1) WScript.CreateObject: Could not create object
                      named "Xyz.XyzWidgetizer".
    

    Has support for the XyzWidgetizer been silently dropped?

    Let's look at the error message more closely. Error 80040154 is REGDB_E_CLASSNOTREG: The class is not registered. Therefore, whatever regsvr32 did, it didn't register the class.

    My psychic powers tell me that you registered the 32-bit version of XYZ.DLL on a 64-bit machine.

    Registering the 32-bit DLL records the entries into the 32-bit registry (because 32-bit programs run in an emulator), and the 32-bit registry is not consulted when you try to create a COM object from a 64-bit application. Letting 64-bit applications see the registration for 32-bit DLLs doesn't actually accomplish anything because you cannot load a 32-bit DLL into a 64-bit process and vice versa—even if a 64-bit process can figure out what DLL it wants, it won't able to load it.

    It so happens that my psychic powers were correct. How did I know that the person asking the question was running the 32-bit version of XYZ on a 64-bit version of Windows? I didn't, but it was the simplest theory that fit the (extremely limited) data. And it didn't involve a global conspiracy.

  • The Old New Thing

    Ready... cancel... wait for it! (part 1)

    • 31 Comments

    One of the cardinal rules of the OVERLAPPED structure is the OVERLAPPED structure must remain valid until the I/O completes. The reason is that the OVERLAPPED structure is manipulated by address rather than by value.

    The word complete here has a specific technical meaning. It doesn't mean "must remain valid until you are no longer interested in the result of the I/O." It means that the structure must remain valid until the I/O subsystem has signaled that the I/O operation is finally over, that there is nothing left to do, it has passed on: You have an ex-I/O operation.

    Note that an I/O operation can complete successfully, or it can complete unsuccessfully. Completion is not the same as success.

    A common mistake when performing overlapped I/O is issuing a cancel and immediately freeing the OVERLAPPED structure. For example:

    // this code is wrong
     HANDLE h = ...; // handle to file opened as FILE_FLAG_OVERLAPPED
     OVERLAPPED o;
     BYTE buffer[1024];
     InitializeOverlapped(&o); // creates the event etc
     if (ReadFile(h, buffer, sizeof(buffer), NULL, &o) ||
         GetLastError() == ERROR_IO_PENDING) {
      if (WaitForSingleObject(o.hEvent, 1000) != WAIT_OBJECT_0) {
       // took longer than 1 second - cancel it and give up
       CancelIo(h);
       return WAIT_TIMEOUT;
      }
      ... use the results ...
     }
     ...
    

    The bug here is that after calling Cancel­Io, the function returns without waiting for the Read­File to complete. Returning from the function implicitly frees the automatic variable o. When the Read­File finally completes, the I/O system is now writing to stack memory that has been freed and is probably being reused by another function. The result is impossible to debug: First of all, it's a race condition between your code and the I/O subsystem, and breaking into the debugger doesn't stop the I/O subsystem. If you step through the code, you don't see the corruption, because the I/O completes while you're broken into the debugger.

    Here's what happens when the program is run outside the debugger:

    ReadFile I/O begins
    WaitForSingleObject I/O still in progress
    WaitForSingleObject times out
    CancelIo I/O cancellation submitted to device driver
    return
    Device driver was busy reading from the hard drive
    Device driver receives the cancellation
    Device driver abandons the rest of the read operation
    Device driver reports that I/O has been canceled
    I/O subsystem writes STATUS_CANCELED to OVERLAPPED structure
    I/O subsystem queues the completion function (if applicable)
    I/O subsystem signals the completion event (if applicable)
    I/O operation is now complete

    When the I/O subsystem receives word from the device driver that the cancellation has completed, it performs the usual operations when an I/O operation completes: It updates the OVERLAPPED structure with the results of the I/O operation, and notifies whoever wanted to be notified that the I/O is finished.

    Notice that when it updates the OVERLAPPED structure, it's updating memory that has already been freed back to the stack, which means that it's corrupting the stack of whatever function happens to be running right now. (It's even worse if you happened to catch it while it was in the process of updating the buffer!) Since the precise timing of I/O is unpredictable, the program crashes with memory corruption that keeps changing each time it happens.

    If you try to debug the program, you get this:

    ReadFile I/O begins
    WaitForSingleObject I/O still in progress
    WaitForSingleObject times out
    Breakpoint hit on Cancel­Io statement
    Stops in debugger
    Hit F10 to step over the CancelIo call I/O cancellation submitted to device driver
    Breakpoint hit on return statement
    Stops in debugger
    Device driver was busy reading from the hard drive
    Device driver receives the cancellation
    Device driver abandons the rest of the read operation
    Device driver reports that I/O has been canceled
    I/O subsystem writes STATUS_CANCELED to OVERLAPPED structure
    I/O subsystem queues the completion function (if applicable)
    I/O subsystem signals the completion event (if applicable)
    I/O operation is now complete
    Look at the OVERLAPPED structure in the debugger
    It says STATUS_CANCELED
    Hit F5 to resume execution
    No memory corruption

    Breaking into the debugger changed the timing of the I/O operation relative to program execution. Now, the I/O completes before the function returns, and consequently there is no memory corruption. You look at the OVERLAPPED structure and say, "See? Immediately on return from the Cancel­Io function, the OVERLAPPED structure has been updated with the result, and the buffer contents are not being written to. It's safe to free them both now. Therefore, this can't be the source of my memory corruption bug."

    Except, of course, that it is.

    This is even more crazily insidious because the OVERLAPPED structure and the buffer are updated by the I/O subsystem, which means that it happens from kernel mode. This means that write breakpoints set by your debugger won't fire. Even if you manage to narrow down the corruption to "it happens somewhere in this function", your breakpoints will never see it as it happens. You're going to see that the value was good, then a little while later, the value was bad, and yet your write breakpoint never fired. You're then going to declare that the world has gone mad and seriously consider a different line of work.

    To fix this race condition, you have to delay freeing the OVERLAPPED structure and the associated buffer until the I/O is complete and anything else that's using them has also given up their claim to it.

       // took longer than 1 second - cancel it and give up
       CancelIo(h);
       WaitForSingleObject(o.hEvent, INFINITE); // added
       // Alternatively: GetOverlappedResult(h, &o, TRUE);
       return WAIT_TIMEOUT;
    

    The Wait­For­Single­Object after the Cancel­Io waits for the I/O to complete before finally returning (and implicitly freeing the OVERLAPPED structure and the buffer on the stack). Better would be to use GetOverlapped­Result with bWait = TRUE, because that also handles the case where the hEvent member of the OVERLAPPED structure is NULL.

    Exercise: If you retrieve the completion status after canceling the I/O (either by looking at the OVERLAPPED structure directly or by using GetOverlapped­Result) there's a chance that the overlapped result will be something other than STATUS_CANCELED (or ERROR_CANCELLED if you prefer Win32 error codes). Explain.

    Exercise: If this example had used Read­File­Ex, the proposed fix would be incomplete. Explain and provide a fix. Answer to come next time, and then we'll look at another version of this same principle.

  • The Old New Thing

    If you want to use GUIDs to identify your files, then nobody's stopping you

    • 37 Comments

    Igor Levicki proposes solving the problem of file extensions by using a GUID instead of a file name to identify a file.

    You can do this already. Every file on an NTFS volume has an object identifier which is formally 16-byte buffer, but let's just call it a GUID. By default a file doesn't have an object identifier, but you can ask for one to be created with FSCTL_CREATE_OR_GET_OBJECT_ID, which will retrieve the existing object identifier associated with a file, or create one if there isn't one already. If you are a control freak, you can use FSCTL_SET_OBJECT_ID to specify the GUID you want to use as the object identifier. (The call fails if the file already has an object identifier.) And of course there is FSCTL_GET_OBJECT_ID to retrieve the object identifier, if any.

    #define UNICODE
    #define _UNICODE
    #include <windows.h>
    #include <stdio.h>
    #include <tchar.h>
    #include <ole2.h>
    #include <winioctl.h>
    
    int __cdecl _tmain(int argc, PTSTR *argv)
    {
     HANDLE h = CreateFile(argv[1], 0,
                     FILE_SHARE_READ | FILE_SHARE_WRITE |
                     FILE_SHARE_DELETE, NULL,
                     OPEN_EXISTING, 0, NULL);
     if (h != INVALID_HANDLE_VALUE) {
      FILE_OBJECTID_BUFFER buf;
      DWORD cbOut;
      if (DeviceIoControl(h, FSCTL_CREATE_OR_GET_OBJECT_ID,
                     NULL, 0, &buf, sizeof(buf),
                     &cbOut, NULL)) {
        GUID guid;
        CopyMemory(&guid, &buf.ObjectId, sizeof(GUID));
        WCHAR szGuid[39];
        StringFromGUID2(guid, szGuid, 39);
        _tprintf(_T("GUID is %ws\n"), szGuid);
      }
      CloseHandle(h);
     }
     return 0;
    }
    

    This program takes a file or directory name as its sole parameter and prints the associated object identifier.

    Big deal, now we have a GUID associated with each file.

    The other half is, of course, using this GUID to open the file:

    #define UNICODE
    #define _UNICODE
    #include <windows.h>
    #include <stdio.h>
    #include <tchar.h>
    #include <ole2.h>
    
    int __cdecl _tmain(int argc, PTSTR *argv)
    {
     HANDLE hRoot = CreateFile(_T("C:\\"), 0,
                     FILE_SHARE_READ | FILE_SHARE_WRITE |
                     FILE_SHARE_DELETE, NULL,
                     OPEN_EXISTING,
                     FILE_FLAG_BACKUP_SEMANTICS, NULL);
     if (hRoot != INVALID_HANDLE_VALUE) {
      FILE_ID_DESCRIPTOR desc;
      desc.dwSize = sizeof(desc);
      desc.Type = ObjectIdType;
      if (SUCCEEDED(CLSIDFromString(argv[1], &desc.ObjectId))) {
       HANDLE h = OpenFileById(hRoot, &desc, GENERIC_READ,
                     FILE_SHARE_READ | FILE_SHARE_WRITE |
                     FILE_SHARE_DELETE, NULL, 0);
       if (h != INVALID_HANDLE_VALUE) {
        BYTE b;
        DWORD cb;
        if (ReadFile(h, &b, 1, &cb, NULL)) {
         _tprintf(_T("First byte of file is 0x%02x\n"), b);
        }
        CloseHandle(h);
       }
      }
      CloseHandle(hRoot);
     }
     return 0;
    }
    

    To open a file by its GUID, you first need to open something—anything—on the volume the file resides on. Doesn't matter what you open; the only reason for having this handle is so that OpenFileById knows which volume you're talking about. In our little test program, we use the C: drive, which means that the file search will take place on the C: drive.

    Next, you fill in the FILE_ID_DESCRIPTOR, saying that you want to open the file by its object identifier, and then it's off to the races with OpenFileById. Just as a proof of concept, we read and print the first byte of the file that was opened as a result.

    Notice that the file you open by its object identifier does not have to be in the current directory. It can be anywhere on the C: drive. As long as you have the GUID for a file, you can open it no matter where it is on the drive.

    You can run these two programs just to enjoy the thrill of opening a file by its GUID. Notice that once you get the GUID for a file, you can move it anywhere on the drive, and OpenFileById will still open it.

    (And if you want to get rid of those pesky drive letters, you can use the volume GUID instead. Now every file is identified by a pair of GUIDs: the volume GUID and the object identifier.)

    So Igor's dream world where all files are referenced by GUID already exists. Why isn't everybody switching over to this utopia of GUID-based file identification?

    You probably know the answer already: Because people prefer to name things with something mnemonic rather than a GUID. Imagine a file open dialog in this dream world. "Enter the GUID of the file you wish to open, or click Browse to see the GUIDs of all the files on this volume so you can pick from a list." How long would this dialog survive?

    For today, you don't have to call me Raymond. You can call me {7ecf65a0-4b78-5f9b-e77c-8770091c0100}, or "91c" for short.

    (And I've totally ignored the fact that using GUIDs to identify files does nothing to solve the problem of trying to figure out what program should be used to open a particular file.)

    Bonus chatter: You can also open files by their file identifer, which is a volume-specific 64-bit value. But I chose to use the GUID both for the extra challenge, and just to show that Igor's dream world already exists.

  • The Old New Thing

    WM_NCHITTEST is for hit-testing, and hit-testing can happen for reasons other than the mouse being over your window

    • 8 Comments

    The WM_NC­HIT­TEST message is sent to your window in order determine what part of the window corresponds to a particular point. The most common reason for this is that the mouse is over your window.

    • The default WM_SET­CURSOR handler uses the result of WM_NC­HIT­TEST to figure out what type of cursor to show. for example, if you return HT­LEFT, then Def­Window­Proc will show the IDC_SIZE­WE cursor.
    • If the user clicks the mouse, the default WM_NC­LBUTTON­DOWN handler uses the result of WM_NC­HIT­TEST to figure out where on the window you clicked. For example, if you return HT­CLOSE, then it will act as if the user clicked on the Close button.

    Although WM_NC­HIT­TEST triggers most often for mouse activity, that is not the only reason why somebody might want to ask, "What part of the window does this point correspond to?"

    • The Window­From­Point function uses WM_NC­HIT­TEST in its quest to figure out which window is under the point you passed in. If you return HT­TRANSPARENT, then it will skip your window and keep looking.
    • Drag/drop operations use the result of WM_NC­HIT­TEST to figure out what part of the window you are dragging over.
    • Accessibility tools use the result of WM_NC­HIT­TEST to help the user understand what's on the screen.
    • Anybody can use the result of WM_NC­HIT­TEST to see how your window is laid out. We used it a few years ago to detect a right-click on the caption button.

    Consider a program that wants to beep when the mouse is over the Close button. This is an artificial example, but you can use your imagination to come up with more realistic ones, like showing a custom mouseover animation or displaying a balloon tip if the document is unsaved. I chose beeping because it requires less code; otherwise, all the details of its implementation would distract from the point of the example.

    Start with the scratch program and make the following changes:

    BOOL g_fInCloseButton = FALSE;
    
    void EnterCloseButton(HWND hwnd)
    {
     if (g_fInCloseButton) return;
     g_fInCloseButton = TRUE;
     MessageBeep(-1); // obviously something more interesting goes here
     TRACKMOUSEEVENT tme = { sizeof(tme), TME_NONCLIENT | TME_LEAVE, hwnd };
     TrackMouseEvent(&tme);
    }
    
    void LeaveCloseButton(HWND hwnd)
    {
     if (g_fInCloseButton) {
      // stop animation, remove balloon, etc.
      g_fInCloseButton = FALSE;
     }
    }
    
    // This code is wrong - see text
    UINT OnNcHitTest(HWND hwnd, int x, int y)
    {
     UINT ht = FORWARD_WM_NCHITTEST(hwnd, x, y, DefWindowProc);
     if (ht == HTCLOSE) {
      EnterCloseButton(hwnd);
     } else {
      LeaveCloseButton(hwnd);
     }
     return ht;
    }
    
    HANDLE_MSG(hwnd, WM_NCHITTEST, OnNcHitTest);
    case WM_NCMOUSELEAVE:
     LeaveCloseButton(hwnd);
     break;
    

    We keep track of whether or not the mouse is in the close button so that we don't double-start the animation or double-cancel it. (For us, this keeps us from beeping when the mouse moves around within the Close button.) When the mouse leaves the close button—either because it moved to another part of the window or because it left the window entirely—we reset the flag.

    When you run this program, it pretty much behaves as intended. But that's because we haven't tried anything interesting yet.

    Merge in the changes from our sample drag/drop program, so now you have a program that both performs drag/drop and which has special Close button behavior.

    Now things get interesting. Run the program and drag out of the client area (triggering the drag/drop behavior) and hover the mouse over the Close button.

    Ow, my ears!

    What happened here?

    When the drag/drop loop is in progress, the mouse is captured to the drag/drop window. Mouse capture means that all mouse messages go to that window (for as long as a mouse button is held down). "I don't care what window you think the mouse is over; it's over me!" Another way of looking at this is that the capture window logically covers the entire screen (for the purpose of determining who gets the mouse message).

    The drag/drop loop wants to know which window is under the drag cursor so it can figure out whose IDropTarget should receive the drag/drop notifications. This WindowFromPoint call triggers a WM_NC­HIT­TEST message, which our program incorrectly interprets as a "the mouse is now in my window". (Since the mouse is captured, the mouse really isn't in your window; it's in the window that has capture because that window is stealing all the mouse input.) It then performs its "The mouse is in the Close button" activities (BEEP). But since the mouse was never in the window to begin with, the Track­Mouse­Event call that requests "let me know when the mouse leaves my window" posts a WM­_NC­MOUSE­LEAVE message immediately. The window then cleans up its "mouse is in the Close button" behaviors, ready for the next cycle.

    And the next cycle begins pretty much as soon as the previous cycle finished, because the mouse is still physically (but not logically) in the Close button.

    Result: Infinite beep loop.

    (The real-life situation that triggered this article was much more complicated than this, involving an animation rather than a beep, but the result was effectively the same: Under the right circumstances, just moving the mouse over the caption resulted in the animation becoming an epileptic-seizure-inducing flicker as the animation continuously started and stopped.)

    As we saw some time ago, the WM_MOUSE­MOVE message is the way to detect that the mouse has entered your window. (Though some people haven't figured this out and continue on their fruitless quest for the WM_MOUSE­ENTER message.)

    In our case, the applicable message is WM_NC­MOUSE­MOVE rather than WM_MOUSE­MOVE, since we are operating on the nonclient area. Therefore, the fix is to move the code that starts the animation from WM_NC­HIT­TEST to WM_NC­MOUSE­MOVE.

    // Delete the old OnNcHitTest function and replace it with this
    void OnNcMouseMove(HWND hwnd, int x, int y, UINT codeHitTest)
    {
     FORWARD_WM_NCMOUSEMOVE(hwnd, x, y, codeHitTest, DefWindowProc);
     if (codeHitTest == HTCLOSE) {
      EnterCloseButton(hwnd);
     } else {
      LeaveCloseButton(hwnd);
     }
     return ht;
    }
    
    // delete HANDLE_MSG(hwnd, WM_NCHITTEST, OnNcHitTest);
    HANDLE_MSG(hwnd, WM_NCMOUSEMOVE, OnNcMouseMove);
    

    Remember, if you want to do something when the mouse enters your window, wait until the mouse actually enters your window. The WM_NC­HIT­TEST message doesn't mean that the mouse is in your window; it just means that somebody is asking, "If the mouse were in your window, what would it be doing?"

  • The Old New Thing

    Shortcuts are serializable objects, which means that they can be stored in places other than just a file

    • 21 Comments

    It's true that the vast majority of the time, people consider the shell shortcut object as synonymous with the .lnk file it is normally saved into, shortcuts need not spend their time in a file. You can put a shortcut anywhere you can save a hunk of bytes. Here's a program that creates a shortcut to the file name passed on the command line (make sure it's a full path), and then serializes the shortcut to a blob of bytes (in the form of a HGLOBAL). Once that's done, it reconstitutes the bytes back into a shortcut object and sucks information out of it.

    #define UNICODE
    #define _UNICODE
    #include <windows.h>
    #include <shlobj.h>
    #include <ole2.h>
    #include <stdio.h>
    #include <tchar.h>
    #include <atlbase.h>
    
    HGLOBAL CreateShellLinkInMemory(PCWSTR pszFile)
    {
     BOOL fSuccess = FALSE;
     HGLOBAL hglob = GlobalAlloc(GMEM_MOVEABLE, 0);
     if (hglob) {
      CComPtr<IStream> spstm;
      if (SUCCEEDED(CreateStreamOnHGlobal(hglob, FALSE, &spstm))) {
       CComPtr<IShellLink> spsl;
       if (SUCCEEDED(spsl.CoCreateInstance(CLSID_ShellLink))) {
        if (SUCCEEDED(spsl->SetPath(pszFile))) {
         CComQIPtr<IPersistStream> spps(spsl);
         fSuccess = spps && SUCCEEDED(spps->Save(spstm, TRUE));
        }
       }
      }
     }
     if (fSuccess) return hglob;
     if (hglob) GlobalFree(hglob);
     return NULL;
    }
    

    After creating the shortcut object, we serialize it into a stream backed by a chunk of memory we record in a HGLOBAL. The shortcut object itself is no longer anywhere to be seen. It's been dehydrated into a pile of dust like in that old Star Trek episode.

    But this time, we know how to bring it back.

    IShellLink *CreateShellLinkFromMemory(HGLOBAL hglob)
    {
     IShellLink *pslReturn = NULL;
     CComPtr<IStream> spstm;
     if (SUCCEEDED(CreateStreamOnHGlobal(hglob, FALSE, &spstm))) {
      CComPtr<IShellLink> spsl;
      if (SUCCEEDED(spsl.CoCreateInstance(CLSID_ShellLink))) {
       CComQIPtr<IPersistStream> spps(spsl);
       if (spps && SUCCEEDED(spps->Load(spstm))) {
        pslReturn = spsl.Detach();
       }
      }
     }
     return pslReturn;
    }
    

    We create a new shortcut object and tell it to restore itself from the chunk of memory we squirreled away. Bingo, the shortcut is back, ready for action.

    int __cdecl wmain(int argc, WCHAR **argv)
    {
     if (SUCCEEDED(CoInitialize(NULL))) {
      HGLOBAL hglob = CreateShellLinkInMemory(argv[1]);
      if (hglob) {
       CComPtr<IShellLink> spsl;
       spsl.Attach(CreateShellLinkFromMemory(hglob));
       if (spsl) {
        WCHAR szTarget[MAX_PATH];
        if (spsl->GetPath(szTarget, MAX_PATH, NULL, 0) == S_OK) {
         wprintf(L"Welcome back, shortcut to %s\n", szTarget);
        }
       }
       GlobalFree(hglob);
      }
      CoUninitialize();
     }
     return 0;
    }
    

    Since shortcuts can be stored anywhere, you can't rely on the file name to distinguish between shortcuts to files and shortcuts to folders because there may not be a file name at all! (What's the file name for our HGLOBAL?) Even if you decide that the convention applies only to shortcuts saved in a file, you've created an additional burden on people who manipulate shortcut files: They have to check whether the target is a file or folder before choosing the file name, and if the target of the shortcut changes, they may have to rename the file as well. This is a real problem for the standard file property sheet: If you change the shortcut target from the Shortcut page, this might change the underlying file name. If you had also made changes to the Security page, it will try to update the security attributes on the old file name, even though the Shortcut page had renamed it. Oops, none of the other property sheet pages work, because they are now operating on a file that no longer exists!

    Exercise: Under what conditions would it be useful to store a shortcut in memory rather than in a file? (Answer.)

  • The Old New Thing

    Any intelligent human being

    • 20 Comments

    The story of The Best reminded me of a classmate from school who was inordinately fond of the phrase any intelligent human being. For example, our classmate would say, "Any intelligent human being would do X" or "It's obvious to any intelligent human being that Y." (I often—but not always—consider myself to be an intelligent human being, yet sometimes the things "any intelligent human being" would do were things I personally wouldn't.) On the other hand, you wouldn't hear our classmate use the phrase in sentences like "I cannot comprehend how any intelligent human being would be fooled by Z."

    Eventually, one of my friends determined that the phrase any intelligent human being was our classmate's replacement for the first person singular pronoun. By "It is obvious to any intelligent human being that Y," our classmate really meant "It is obvious to me that Y."

    Thenceforth, we referred to our classmate as Any Intelligent Human Being (or AIHB for short, and capitalized since it's now a proper noun). Example: "I saw Any Intelligent Human Being in the cafeteria yesterday."

  • The Old New Thing

    Why does WaitForMultipleObjects return ERROR_INVALID_PARAMETER when all the parameters look valid to me?

    • 14 Comments

    A customer asked for assistance with the WaitForMultipleObjects function:

    I am getting ERROR_INVALID_PARAMETER when calling Wait­For­Multiple­Objects even though all the parameters are valid as far as I can tell. I've narrowed it down to this simple program.

    int main()
    {
     int i;
     HANDLE Handles[4];
    
     // Create the events
     for (i = 0; i < 4; i++) {
      Handles[i] = CreateEvent(NULL, FALSE, FALSE, TEXT("Test"));
      if (Handles[i] == NULL) {
       printf("Failed to create event - test failed\n"); return 0;
      }
     }
    
     // Set them all to signaled
     for (i = 0; i < 4; i++) SetEvent(Handles[i]);
    
     // Wait for all of them - we expect this to return WAIT_OBJECT_0
     printf("WaitForMultipleObjects returned %d\n",
            WaitForMultipleObjects(4, Handles, TRUE, INFINITE));
    
     return 0;
    }
    

    First of all, thank you for narrowing the issue down to a minimal program that illustrates the problem. You'd be surprised how often a customer says, "I'm having problem with function X. Here's a program that illustrates the problem." And then attaches a huge project that doesn't compile because it is written in some development environment different from the one you have on your machine.

    The problem here is that you are passing four handles to the same event to Wait­For­Multiple­Objects with the bWait­All parameter set to TRUE. The Wait­For­Multiple­Objects function rejects duplicates if you ask it to wait for all of the objects. Why is that?

    Well, consider this program: It creates a named auto-reset event (as is "obvious" from the FALSE second parameter passed to Create­Event) and stores a handle to it in Handles[0]. The second through fourth calls to Create­Event merely create new handles to the same auto-reset event because the name matches an existing event. The second loop sets that same event four times. And then the Wait­For­Multiple­Objects asks to wait for all of the handles to be signaled. But since all four handles refer to the same object, it's being asked to wait until the event has reached the state where the wait can complete four times simultaneously. (Huh?)

    Recall that Wait­For­Multiple­Objects does not alter the state of any of the waited objects until the wait completes. If you ask it to wait for both an event and a semaphore, and the event is signaled but the semaphore is not, then the function will leave the event signaled while it waits for the semaphore. Only when all the items being waited for are signaled will the Wait­For­Multiple­Objects function perform whatever actions are appropriate for acquiring a signaled object and return.

    Okay, so we asked it to wait on the same auto-reset event four times. But that's nonsense: An auto-reset event is just a stupid semaphore which can have at most one token. But in order for the wait to succeed, it needs four tokens. That's never going to happen, so the wait is nonsensical.

    More generally speaking, Wait­For­Multiple­Objects returns ERROR_INVALID_PARAMETER if you pass bWaitAll = TRUE and there are any duplicates in the handle array (either identical handles, or different handles to the same underlying object). It doesn't try to puzzle out the objects and say, "Well, let me see if this is a reasonable combination of objects to wait on more than once"; it just sees the duplicate and says "Forget this!"

    Going back to the customer's original problem: We asked why they were creating four handles to the same object, and what they expected when waiting for an auto-reset event to have four available tokens (which it never will), and the customer admitted that it was just an error in their code. The original version of the code used a named event and waited on it with Wait­For­Single­Object, and when they modified the code to make it support multiple events, they forgot to give each event a different name.

  • The Old New Thing

    What happens when you email the people in the I'm a PC commercial?

    • 20 Comments

    In 2008, the first I'm a PC ad aired, opening with Sean Siler doing an impression of John Hodgman portraying a PC, and continuing with montage of people proudly announcing, "I'm a PC!"

    Accompanying the first four people to appear on screen are email addresses. The addresses are live (or at least they were when the campaign launched), and if you write to them, you get an autoresponse.

    I've heard that the most popular of the four email addresses in terms of incoming volume was not Sean Siler's, nor was it the one belonging to Bill Gates. Rather, the most messages arrived from people trying to contact the young woman in the server room.

    And it wasn't even close. We're talking a factor of like 5.[citation needed]

    As it happens, she is a developer on the shell team, and by an astonishing coincidence, her office number at the time was 1337.

    Sadly, that one second of fame took its toll on my colleague, who now deeply wishes for the permanent destruction of that brief clip of her saying "I'm a PC."

    And no, I won't forward your email to her.

Page 1 of 3 (28 items) 123