• The Old New Thing

    Two brief reminiscences on the Windows XP "Comments?" button

    • 35 Comments

    In beta versions of Windows XP, there was special code in the window manager to give every window a link in the upper right corner called "Comments?" which if clicked on displayed a dialog that allowed you to submit feedback to Microsoft about that window.

    Since this was a beta release, there was no anonymity when you submitted feedback. (You signed away your anonymity when you agreed to the special beta license agreement and typed in your beta ID number.) Yet we got more than one feedback submission that begin, "Hi, I pirated this copy of Windows XP, and here's some feedback."

    In its initial incarnation, the word in the title bar was "Lame", but people with a weaker sense of humor changed it to the less confrontational "Comments?". The name "Lame" came from a recurring sketch on local comedy show Almost Live! called "The Lame List, or What's Weak This Week (brought to you with the help of Seattle's Heavy Metal community)".

  • The Old New Thing

    Modality, part 5: Setting the correct owner for modal UI

    • 10 Comments

    Here is the very simple fix for the buggy program we presented last time.

    void OnChar(HWND hwnd, TCHAR ch, int cRepeat)
    {
      switch (ch) {
      case ' ':
        MessageBox(hwnd, TEXT("Message"), TEXT("Title"), MB_OK);
        if (!IsWindow(hwnd)) MessageBeep(-1);
        break;
      }
    }
    

    We have fixed the problem by passing the correct owner window for the modal UI. Since MessageBox is modal, it disables the owner while the modal UI is being displayed, thereby preventing the user from destroying or changing the owner window's state when it is not expecting it.

    This is why all the shell functions that can potentially display UI accept a window handle as one of its parameters. They need to know which window to use as the owner for any necessary UI dialogs. If you call such functions from a thread that is hosting UI, you must pass the handle to the window you want the shell to use as the UI owner. If you pass NULL (or worse, GetDesktopWindow), you may find yourself in the same bad state that our buggy sample program demonstrated.

    If you are displaying a modal dialog from another modal dialog, it is important to pass the correct window as the owner for the second dialog. Specifically, you need to pass the modal dialog initiating the sub-dialog and not the original frame window. Here's a stack diagram illustrating:

     MainWindow
      DialogBox(hwndOwner = main window) [dialog 1]
       ... dialog manager ...
        DlgProc
         DialogBox(hwndOwner = dialog 1) [dialog 2]
    

    If you mess up and pass the main window handle when creating the second modal dialog, you will find yourself back in a situation analogous to what we had last time: The user can dismiss the first dialog while the second dialog is up, leaving its stack frames orphaned.

  • The Old New Thing

    Modality, part 4: The importance of setting the correct owner for modal UI

    • 19 Comments

    If you decide to display some modal UI, it is important that you set the correct owner for that UI. If you fail to heed this rule, you will find yourself chasing some very strange bugs.

    Let's return to our scratch program and intentionally introduce a bug related to incorrect owner windows, so that we can see the consequences.

    void OnChar(HWND hwnd, TCHAR ch, int cRepeat)
    {
      switch (ch) {
      case ' ':
        // Wrong!
        MessageBox(NULL, TEXT("Message"), TEXT("Title"), MB_OK);
        if (!IsWindow(hwnd)) MessageBeep(-1);
        break;
      }
    }
    
    // Add to WndProc
        HANDLE_MSG(hwnd, WM_CHAR, OnChar);
    

    Run this program, press the space bar, and instead of dismissing the message box, click the "X" button in the corner of the main window. Notice that you get a beep before the program exits.

    What happened?

    The beep is coming from our call to the MessageBeep function, which in turn is telling us that our window handle is no longer valid. In a real program which kept its state in per-window instance variables (instead of in globals like we do), you would more likely crash because all the instance variables would have gone away when the window was destroyed. In this case, the window was destroyed while inside a nested modal loop. As a result, when control returned to the caller, it is now a method running inside an object that has been destroyed. Any access to an instance variable is going to access memory that was already freed, resulting in memory corruption or an outright crash.

    Here's an explanation in a call stack diagram:

     WinMain
      DispatchMessage(hwnd, WM_CHAR)
       OnChar
        MessageBox(NULL)
         ... modal dialog loop ...
         DispatchMessage(hwnd, WM_CLOSE)
          DestroyWindow(hwnd)
           WndProc(WM_DESTROY)
            ... clean up the window ...
    

    When you clean up the window, you typically destroy all the data structures associated with the window. But notice that you are freeing data structures that are still being used by the OnChar handler deeper in the stack. Eventually, control unwinds back to the OnChar, which is now running with an invalid instance pointer. (If you believe in C++ objects, you would find that its "this" pointer has gone invalid.)

    This was caused by failing to set the correct owner for the modal MessageBox call, allowing the user to interact with the frame window at a time when the frame window isn't expecting to have its state changed.

    Even more problematic, the user can switch back to the frame window and hit the space bar again. The result: Another message box. Repeat another time and you end up with a stack that looks like this:

     WinMain
      DispatchMessage(hwnd, WM_CHAR)
       OnChar
        MessageBox(NULL)
         ... modal dialog loop ...
         DispatchMessage(hwnd, WM_CHAR)
           OnChar
    	MessageBox(NULL)
    	 ... modal dialog loop ...
    	 DispatchMessage(hwnd, WM_CHAR)
    	   OnChar
    	    MessageBox(NULL)
    	     ... modal dialog loop ...
    

    There are now four top-level windows, all active. If the user dismisses them in any order other than the reverse order in which they were created, you're going to have a problem on your hands. For example, if the user dismisses the second message box first, the part of the stack corresponding to that nesting level will end up returning to a destroyed window when the third message box is finally dismissed.

    The fix is simple, and we'll pick up there next time.

  • The Old New Thing

    Modality, part 3: The WM_QUIT message

    • 23 Comments

    After our two quick introductions to modality, we're now going to dig in a little deeper.

    The trick with modality is that when you call a modal function, the responsibility of message dispatch is handled by that function rather than by your main program. Consequently, if you have customized your main program's message pump, those customizations are lost once you lose control to a modal loop.

    The other important thing about modality is that a WM_QUIT message always breaks the modal loop. Remember this in your own modal loops! If ever you call the PeekMessage function or The [typo fixed 10:30am] GetMessage function and get a WM_QUIT message, you must not only exit your modal loop, but you must also re-generate the WM_QUIT message (via the PostQuitMessage message) so the next outer layer will see the WM_QUIT message and do its cleanup as well. If you fail to propagate the message, the next outer layer will not know that it needs to quit, and the program will seem to "get stuck" in its shutdown code, forcing the user to terminate the process the hard way.

    In a later series, we'll see how this convention surrounding the WM_QUIT message is useful. But for now, here's the basic idea of how your modal loops should re-post the quit message to the next outer layer.

    BOOL WaitForSomething(void)
    {
      MSG msg;
      BOOL fResult = TRUE; // assume it worked
      while (!SomethingFinished()) {
        if (GetMessage(&msg, NULL, 0, 0)) {
          TranslateMessage(&msg);
          DispatchMessage(&msg);
        } else {
          // We received a WM_QUIT message; bail out!
          CancelSomething();
          // Re-post the message that we retrieved
          PostQuitMessage(msg.wParam);
          fResult = FALSE; // quit before something finished
          break;
        }
      }
      return fResult;
    }
    

    Suppose your program starts some operation and then calls WaitForSomething(). While waiting for something to finish, some other part of your program decides that it's time to exit. (Perhaps the user clicked on a "Quit" button.) That other part of the program will call PostQuitMessage(wParam) to indicate that the message loop should terminate.

    The posted quit message will first be retrieved by the GetMessage in the WaitForSomething function. The GetMessage function returns FALSE if the retrieved message is a WM_QUIT message. In that case, the "else" branch of the conditional is taken, which cancels the "Something" operation in progress, then posts the quit message back into the message queue for the next outer message loop to handle.

    When WaitForSomething returns, control presumably will fall back out into the program's main message pump. The main message pump will then retrieve the WM_QUIT message and do its exit processing before finally exiting the program.

    And if there were additional layers of modality between WaitForSomething and the program's main message pump, each of those layers would retrieve the WM_QUIT message, do their cleanup, and then re-post the WM_QUIT message (again, via PostQuitMessage) before exiting the loop.

    In this manner, the WM_QUIT message gets handed from modal loop to modal loop, until it reaches the outermost loop, which terminates the program.

    "But wait," I hear you say. "Why do I have to do all this fancy WM_QUIT footwork? I could just have a private little global variable named something like g_fQuitting. When I want the program to quit, I just set this variable, and all of my modal loops check this variable and exit prematurely if it is set. Something like this:

    BOOL MyWaitForSomething(void) // code in italics is wrong
    {
      MSG msg;
      while (!SomethingFinished()) {
        if (g_fQuitting) {
        CancelSomething();
          return FALSE;
        }
        if (GetMessage(&msg, NULL, 0, 0)) {
          TranslateMessage(&msg);
          DispatchMessage(&msg);
        }
      }
      return TRUE;
    }
    

    And so I can solve the problem of the nested quit without needing to do all this PostQuitMessage rigamarole."

    And you'd be right, if you controlled every single modal loop in your program.

    But you don't.

    For example, when you call the DialogBox function, the dialog box code runs its own private modal loop to do the dialog box UI until you get around to calling the EndDialog function. And whenever the user clicks on any of your menus, Windows runs its own private modal loop to do the menu UI. Indeed, even the resizing of your application's window is handled by a Windows modal loop.

    Windows, of course, has no knowledge of your little g_fQuitting variable, so it has no idea that you want to quit. It is the WM_QUIT message that serves this purpose of co-ordinating the intention to quit among separate parts of the system.

    Notice that this convention regarding the WM_QUIT message cuts both ways. You can use this convention to cause modal loops to exit (we'll see more of this later), but it also obliges you to respect this convention so that other components (including the window manager itself) can get your modal loops to exit.

  • The Old New Thing

    Modality, part 2: Code-modality vs UI-modality

    • 19 Comments
    Last time, we saw an example of code that was UI-modal but not code-modal. The opposite is also true: You can have code-modality without UI-modality. In fact, this is far more common than the UI-modal-but-not-code-modal scenario.

    You encounter modal loops without a visible change in UI state when you drag the scroll bar thumb, drag the window caption, display a pop-up menu, or initiate an OLE drag/drop operation, among other places. Any time a nested message loop is constructed, you have code modality.

    One example is given in MSDN, where the MsgWaitForMultipleObjects function is used to construct a message loop without any modal UI. (We discussed some of the gotchas of the MsgWaitForMultipleObjects function in an earlier entry.)

  • The Old New Thing

    Modality, part 1: UI-modality vs code-modality

    • 26 Comments

    From the end-users' point of view, modality occurs when the users are locked into completing a task once it is begun, with the only escape being to cancel the entire operation. Opening a file is an example of a modal operation: Once the "Open" command has been selected, users have no choice but to select a file for opening (or to cancel the operation). While attempting to open a document, the users cannot interact with the existing document (for example, scroll it around to look for some text that would give a clue as to what file to open next).

    From a programmer's point of view, modality can be viewed as a function that performs some UI and doesn't return until that UI is complete. In other words, modality is a nested message loop that continues processing messages until some exit condition is reached. In our example above, the modality is inherent in the GetOpenFileName function, which does not return until the user selects a filename or cancels the dialog box.

    Note that these concepts do not necessarily agree. You can create something that is UI-modal—that is, does not let the user interact with the main window until some other action is complete—while internally coding it as a non-modal function.

    Let's code up an example of this behavior, to drive the point home.

    As always, start with our scratch program.

    #include <commdlg.h>
    
    HWND g_hwndFR;
    TCHAR g_szFind[80];
    FINDREPLACE g_fr = { sizeof(g_fr) };
    UINT g_uMsgFindMsgString;
    
    void CreateFindDialogUIModally(HWND hwnd)
    {
      if (!g_hwndFR) {
        g_uMsgFindMsgString = RegisterWindowMessage(FINDMSGSTRING);
        if (g_uMsgFindMsgString) {
          g_fr.hwndOwner = hwnd;
          g_fr.hInstance = g_hinst;
          g_fr.lpstrFindWhat = g_szFind;
          g_fr.wFindWhatLen = 80;
          g_hwndFR = FindText(&g_fr);
        }
      }
    }
    
    void OnChar(HWND hwnd, TCHAR ch, int cRepeat)
    {
      switch (ch) {
      case ' ': CreateFindDialogUIModally(hwnd); break;
      }
    }
    
    void OnFindReplace(HWND hwnd, FINDREPLACE *pfr)
    {
      if (pfr->Flags & FR_DIALOGTERM) {
          DestroyWindow(g_hwndFR);
          g_hwndFR = NULL;
      }
    }
    
    // Add to WndProc
        HANDLE_MSG(hwnd, WM_CHAR, OnChar);
    
        default:
          if (uiMsg == g_uMsgFindMsgString && g_uMsgFindMsgString) {
            OnFindReplace(hwnd, (FINDREPLACE*)lParam);
          }
          break;
    
    // Edit WinMain
        while (GetMessage(&msg, NULL, 0, 0)) {
            if (g_hwndFR && IsDialogMessage(g_hwndFR, &msg)) {
            } else {
              TranslateMessage(&msg);
              DispatchMessage(&msg);
            }
        }
    

    This is an unexciting example of a modeless dialog; in our case, the Find dialog is displayed when you hit the space bar. Observe that you can click back to the main window while the Find dialog is up; that's because the Find dialog is modeless. As is typical for modeless dialogs, dispatching its messages is handled in the main message loop with a call to the IsDialogMessage function.

    We can turn this into a UI-modal dialog very simply:

    void CreateFindDialogUIModally(HWND hwnd)
    {
      if (!g_hwndFR) {
        g_uMsgFindMsgString = RegisterWindowMessage(FINDMSGSTRING);
        if (g_uMsgFindMsgString) {
          g_fr.hwndOwner = hwnd;
          g_fr.hInstance = g_hinst;
          g_fr.lpstrFindWhat = g_szFind;
          g_fr.wFindWhatLen = 80;
          g_hwndFR = FindText(&g_fr);
          if (g_hwndFR) {
            EnableWindow(hwnd, FALSE);
          }
        }
      }
    }
    
    void OnFindReplace(HWND hwnd, FINDREPLACE *pfr)
    {
      if (pfr->Flags & FR_DIALOGTERM) {
          EnableWindow(hwnd, TRUE);
          DestroyWindow(g_hwndFR);
          g_hwndFR = NULL;
      }
    }
    

    Notice that we carefully observed the rules for enabling and disabling windows.

    When you run this modified program, everything seems the same except that the Find dialog is now modal. You can't interact with the main window until you close the Find dialog. The Find dialog is modal in the UI sense. However, the code is structured in the non-modal manner. There is no dialog loop; the main window loop dispatches dialog messages as necessary.

    One typically does not design one's modal UI in this manner because it makes the code harder to structure. Observe, for example, that the code to manage the dialog box is scattered about and the management of the dialog needs to be handled as a state machine since each phase returns back to the main message loop.

  • The Old New Thing

    MsgWaitForMultipleObjects and the queue state

    • 37 Comments

    One danger of the MsgWaitForMultipleObjects function is calling it when there are already messages waiting to be processed, because MsgWaitForMultipleObjects returns only when there is a new event in the queue.

    In other words, consider the following scenario:

    • PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) returns TRUE indicating that there is a message.
    • Instead of processing the message, you ignore it and call MsgWaitForMultipleObjects.

    This wait will not return immediately, even though there is a message in the queue. That's because the call to PeekMessage told you that a message was ready, and you willfully ignored it. The MsgWaitForMultipleObjects message tells you only when there are new messages; any message that you already knew about doesn't count.

    A common variation on this is the following:

    • MsgWaitForMultipleObjects returns that there is a message.
    • You call PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) and process that message.
    • You call MsgWaitForMultipleObjects to wait for more messages.

    If it so happens that there were two messages in your queue, the MsgWaitForMultipleObjects does not return immediately, because there are no new messages; there is an old message you willfully ignored, however.

    When MsgWaitForMultipleObjects tells you that there is a message in your message queue, you have to process all of the messages until PeekMessage returns FALSE, indicating that there are no more messages.

    Note, however, that this sequence is not a problem:

    • PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) returns FALSE indicating that there is no message.
    • A message is posted into your queue.
    • You call MsgWaitForMultipleObjects and include the QS_ALLPOSTMESSAGE flag.

    This wait does return immediately, because the incoming posted message sets the "There is a new message in the queue that nobody knows about" flag, which QS_ALLPOSTMESSAGE matches and therefore causes MsgWaitForMultipleObjects to return immediately.

    The MsgWaitForMultipleObjectsEx function lets you pass the MWMO_INPUTAVAILABLE flag to indicate that it should check for previously-ignored input.

    Armed with this knowledge, explain why the observed behavior with the following code is "Sometimes my program gets stuck and reports one fewer record than it should. I have to jiggle the mouse to get the value to update. After a while longer, it falls two behind, then three..."

    // Assume that there is a worker thread that processes records and
    // posts a WM_NEWRECORD message for each new record.
    
    BOOL WaitForNRecords(HANDLE h, UINT cRecordsExpected)
    {
      MSG msg;
      UINT cRecords = 0;
      while (true) {
        switch (MsgWaitForMultipleObjects(1, &h,
                             FALSE, INFINITE, QS_ALLINPUT)) {
        case WAIT_OBJECT_0:
          DoSomethingWith(h); // event has been signalled
          break;
        case WAIT_OBJECT_1:
          // we have a message - peek and dispatch it
          if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
          }
          if (SendMessage(hwndNotify, WM_GETRECORDCOUNT,
                          0, 0) >= cRecordsExpected) {
            return TRUE; // we got enough records
          }
          break;
        default:
          return FALSE; // unexpected failure
        }
      }
    }
    
  • The Old New Thing

    Limitations of the shell animation control

    • 15 Comments

    The Animation control in the shell common controls library supports only a very limited class of AVI files.

    • The AVI must be non-interleaved.
    • The AVI must have exactly one video stream.
    • The AVI may not have an audio stream.
    • The AVI may not use palette changes.
    • The AVI must be either uncompressed or BI_RLE8-compressed.

    Why all these restrictions?

    Because the purpose of the Animation control is to be able to show simple animations. If you have a complex animation, you should be using one of the more advanced animation controls, like the MCIWnd window class.

    There would be no benefit to adding all the advanced AVI playback features were added to the shell Animation control. The result would just be a pointless clone of MCIWnd.

    This is something you need to keep in mind when designing a component whose original design goal is to be a simpler version of some other component. You must resist the urge to add features from that other component to your component. If you succumb, then you will end up with a component that does everything the original component did, even though your goal was to be a simpler version. So why did you write it? You spent months writing something that already exists.

  • The Old New Thing

    You cannot globally reserve user-mode address space

    • 41 Comments

    Occasionally, somebody asks for a way to reserve user-mode address space globally. In other words, they want to allocate address space in all processes in the system (current and future). Typically this is because they want to map some memory into each process and don't want to go through the trouble of designing the shared memory blocks so that they can be relocated.

    This is obviously not possible.

    Why obviously? Well, imagine if this were possible.

    "Imagine if this were possible" is one of the logic tests you can apply to a theory to see if it can possibly be true. Here, we're using it to determine whether a proposed behavior is possible. [Typo fixed 10am.] (There is a corresponding thought experiment, "Imagine if things actually worked that way.")

    What are the consequences of global address space allocation?

    Well, first of all, there's no guarantee that by the time you request your global address space, there will be any available addresses at all. Consider a program that uses every last scrape of user-mode address space. (It can do this by just calling VirtualAlloc(MEM_RESERVE) in a loop; since no memory is being committed, this doesn't actually require 2GB of RAM.) Run such a program and no global address space allocations are possible until that program exits.

    So even if it were possible, it wouldn't be reliable. Your program would have to be prepared for the situation where no global address space was available. Since you're going to have to write fallback code anyway, you didn't save yourself any work.

    Next, suppose it were possible, that there were some imaginary GlobalVirtualAlloc function. Well, then I can write a program that calls this imaginary function in a loop, sucking up all available global virtual address space, and run it as a non-administrator.

    I just violated security. (The general principle here is that a non-administrator should not be allowed to affect other users. We've already seen one scenario where a non-administrator can crash a program running as administrator due to insecure use of shared memory.)

    My imaginary program sucked up all global virtual address space, reducing the address space available to programs running as administrator. If there aren't many programs running on the system, my imaginary program will probably be able to suck up quite a lot of address space this way, which in turn will cause a corresponding reduction in address space to those administrative programs. I can therefore cause those programs to run out of address space sooner, resulting in premature failure (denial of service).

    Yes, you could decide that "global" address space reservation is available only to administrators, but that wouldn't help a lazy programmer, since the program would not work when run as a non-administrator - you have to write the fallback code anyway. Or you could decide that "global" address space reservation applies only to users within the same session with the same security token, but again, you have to write the fallback code anyway if the global reservation fails; you didn't save yourself any work. When the time comes, you can use VirtualAlloc and pass a preferred address to try to get the memory at that address; if it fails, then use the fallback code that you already wrote.

    The moral of the story is that each process gets its own address space and each process manages its address space independently of other processes. (Of course, a process can grant a user PROCESS_VM_OPERATION permission, which gives that user permission to mess with the address space of that process. But changes to that process's address space does not affect the address space of other processes.)

  • The Old New Thing

    How to act like you know Chinese even though you don't

    • 34 Comments

    I was riding in a car with a friend when a Chinese pop album spun up on the CD. I made a remark about the song, and my friend, knowing that I was studying Mandarin, asked me, "Do you know what she's singing about?"

    I immediately replied, "She's singing about love."

    "Wow," my friend responded. "Your Chinese is quite good."

    "Nope," I said. "But all pop songs are about love."

    If somebody asks you what a pop song is about, you can answer "love", and you've got a very high chance of being correct.

Page 372 of 438 (4,378 items) «370371372373374»