February, 2005

  • The Old New Thing

    How to detect programmatically whether you are running on 64-bit Windows

    • 46 Comments

    To detect programmatically whether your 32-bit program is running on 64-bit Windows, you can use the IsWow64Process function.

    Do not do as some people do and hard-code the list of 64-bit processors. You'd think that after the hard-coded list of 64-bit processors changed the first time (when x64 was added to ia64), people would have learned their lesson.

    But how do you detect programmatically from your 64-bit process whether you are running on 64-bit Windows? Easy.

    BOOL Is64BitProcessRunningOn64BitWindows()
    {
     return TRUE;
    }
    

    The fact that your 64-bit program is running at all means that you are running on 64-bit Windows! If it were a 32-bit machine, your program wouldn't be able to run.

    It's like asking the question, "Is the power on?" If there were no power, your program wouldn't be able to ask the question.

    Of course, if you want a single source code base that can be compiled both as a 32-bit program and as a 64-bit program, you have a tiny amount of work to do.

    BOOL Is64BitWindows()
    {
    #if defined(_WIN64)
     return TRUE;  // 64-bit programs run only on Win64
    #elif defined(_WIN32)
     // 32-bit programs run on both 32-bit and 64-bit Windows
     // so must sniff
     BOOL f64 = FALSE;
     return IsWow64Process(GetCurrentProcess(), &f64) && f64;
    #else
     return FALSE; // Win64 does not support Win16
    #endif
    }
    

    I threw in a branch for 16-bit programs if you're crazy enough to be still writing 16-bit Windows programs.

  • The Old New Thing

    Windowless controls are not magic

    • 61 Comments

    It seems that when people notice that the Internet Explorer rendering engine doesn't use HWNDs for screen elements, they think that Internet Explorer is somehow "cheating" and doing something "undocumented" and has an "unfair advantage".

    Nevermind that windowless controls have been around since 1996. They aren't magic. Mind you, they're a lot of work, but they aren't magic.

    Clearly Internet Explorer cannot create a real HWND for every element in an HTML page. There is a limit of 10,000 USER handles per process, and you are likely to run out of desktop heap long before then.

    The Internet Explorer team went and reimplemented all of the controls that a web page would need. They have their own windowless checkbox control, a windowless listbox control, a windowless edit box, and so on. In addition to reproducing all the functionality of the windowed controls, the Internet Explorer folks also had to reproduce the "look" of the windowed controls, down to the last pixel. (Functions like DrawThemeBackground and DrawFrameControl prove extremely helpful here.)

    If I recall correctly, the only element that is still windowed is the <SELECT> element.

    If you squint, you can see some places where they didn't quite nail it. For example, if you right-click in a text box, options like "Right to left reading order" and "Insert Unicode control character" are missing. As another example, notice that IE's scroll bars do not light up when you hover over them.

    Remember, I never worked on Internet Explorer; all I know is what I learn from people from that team. (jeffdav for example, joins the shell team for lunch nearly every day.) If you have questions about Internet Explorer, you would likely have much better success asking the Internet Explorer team yourself via their team blog.

  • The Old New Thing

    The history of the Windows PowerToys

    • 81 Comments

    During the development of Windows 95, as with the development of any project, the people working on the project write side programs to test the features they are adding or to prototype a feature. After Windows 95 shipped, some of those programs were collected into the first edition of the Windows 95 Power Toys.

    As I recall, the first edition contained the following toys:

    CabView
    This was a handy internal tool which also served as a test of the shell folder design.

    CDAutoPlay, DeskMenu, FlexiCD, QuickRes
    These were side toys originally written by shell developers for their own personal use.

    Command Prompt Here, Explore From Here
    These were proof-of-concept toys which tested the shell command extension design.

    Round Clock
    This was a program to test regional windows.

    Shortcut Target Menu
    This was a feature idea that didn't quite make it.

    I wasn't around when the decision was made to package these toys up and ship them, so I don't know what the rule was for deciding what was PowerToy-worthy and what wasn't. Nor do I know where the name PowerToy came from. (Probably somebody just made it up because it sounded neat.)

    Upon the enormous success of the PowerToys, a second edition was developed. This time, people knew that they were writing a PowerToy, as opposed to the first edition of the PowerToys which was merely cobbled together from stuff lying around. The second edition of the Windows 95 PowerToys added FindX, Send To X, the Telephony Locator Selector, XMouse, and Tweak UI.

    Later, the kernel team released their own set of toys, known as the Windows 95 Kernel Toys. Alas, the original blurb text is not on the Microsoft downloads site, but here's an archived copy. (In reality, it was I who wrote all of the Kernel Toys, except for the Time Zone Editor, which came from the Windows NT Resource Kit. I also wrote the somewhat whimsical original blurb.)

    This was all back in the day when it was easy to put up something for download. No digital signatures, no virus checking, no paperwork. Just throw it up there and watch what happens. Today, things are very different. Putting something up for download is a complicated process with forms to fill out in triplicate and dark rooms with card readers. I wouldn't be surprised if an abandoned salt mine in Montana were somehow involved.

    Nowadays, every team at Microsoft seems to have their own PowerToys, trading on the good name of the Windows shell team who invented the whole PowerToys idea. (As far as I can tell, we don't get any royalties from other divisions calling their toys "PowerToys".) A quick check reveals the following PowerToys available for download from Microsoft; I may have missed some.

    (Plus, of course, the Windows XP PowerToys, which does come from the shell team. The Internet Explorer team originally called their stuff PowerToys, but they later changed the name to Web Accessories, perhaps to avoid the very confusion I'm discussing here.)

    What's frustrating is that since they are all called "PowerToys", questions about them tend to go to the shell team, since we are the ones who invented PowerToys. We frequently have to reply, "Oh, no, you're having a problem with the XYZ PowerToys, not the classic Windows PowerToys. We're the folks who do the classic Windows PowerToys."

    Even the blog name "PowerToys" has been co-opted by the Visual Studio team to promote their Powertoys for Visual Studio 2003.

    Some people claim that Tweak UI was written because Microsoft got tired of responding to customer complaints. I don't know where they got that from. Tweak UI was written because I felt like writing it.

    That page also says that sometimes PowerToys vanish without warning. That's true. A few years ago, all the Windows XP PowerToys were taken down so they could be given a security review. Some of them didn't survive and didn't come back. Other times, a PowerToy will be pulled because a serious bug was found. Since PowerToys are spare-time projects, it can take a very long time for a bug to get fixed, tested, and re-published. For example, the HTML Slide Show Wizard was pulled after a (somewhat obscure) data-loss bug was found. Fixing the bug itself took just a few days, but testing and filling out all the associated paperwork took six months.

    There's no moral to this story. Just a quick history lesson.

  • 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

    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 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

    "Support our troops" wristbands provide only 1/3 support

    • 43 Comments

    Over at Slate, Timothy Noah notes how much money from so-called "awareness bracelets" actually go to the causes they purport to champion.

    In summary,

    Bracelet Vendor Cause Portion that
    benefits cause
    Yellow "Livestrong" Lance Armstrong Foundation supporting cancer survivors all
    Cinnamon Tsunami Relief Red Cross, UNICEF, CARE 1/2
    Camo-green 7-11 USO 1/3
    Light blue Awareness Depot Tsunami disaster relief
    (unspecified charity)
    1/10
    Yellow Awareness Depot Support our troops 0

    I find it ironic that the "Support our troops" bracelet doesn't actually support our troops. And is yellow really the right color to use to demonstrate support for the military?

  • The Old New Thing

    LoadLibraryEx(DONT_RESOLVE_DLL_REFERENCES) is fundamentally flawed

    • 25 Comments

    There is a flag to the LoadLibraryEx function called DONT_RESOLVE_DLL_REFERENCES. The documentation says,

    If this value is used, and the executable module is a DLL, the system does not call DllMain for process and thread initialization and termination. Also, the system does not load additional executable modules that are referenced by the specified module.

    If you are planning only to access data or resources in the DLL, it is better to use LOAD_LIBRARY_AS_DATAFILE.

    In my opinion, the above text that "suggests" the LOAD_LIBRARY_AS_DATAFILE flag is not strong enough.

    DONT_RESOLVE_DLL_REFERENCES is a time bomb.

    Look carefully at what the flag does and doesn't do. The module is loaded into memory, but its initialization function is not called and no dependent DLLs are loaded. [Typo fixed, 10am.] As a result, you cannot run code from this DLL. (More accurately, if you try, it will crash because the DLL hasn't initialized itself and none of its imports to DLLs have been resolved.) However, unlike the LOAD_LIBRARY_AS_DATAFILE flag, the loaded DLL can be found by GetModuleHandle and can be used by GetProcAddress.

    Clearly, GetProcAddress is a bad idea for something loaded by DONT_RESOLVE_DLL_REFERENCES, because as we already noted, you can't run any code from the DLL. What's the point of getting a procedure address from a DLL if you can't call it, after all?

    The GetModuleHandle part triggers the time bomb.

    It is common for somebody to call GetModuleHandle to see if a DLL is loaded, and if so, use GetProcAddress to get a procedure address and call it. If the DLL had been loaded with DONT_RESOLVE_DLL_REFERENCES, both the GetModuleHandle will succeed, but the resulting function will crash when called. The code doing this has no idea that the DLL was loaded with DONT_RESOLVE_DLL_REFERENCES; it has no way of protecting itself.

    (Note that code that does this is unsafe anyway, because the code that originally loaded the DLL might decide to do a FreeLibrary on another thread, causing the code to be ripped out from underneath the first thread. This second problem can be "fixed" by using GetModuleHandleEx, which can be instructed to increment the DLL reference count, but that doesn't fix the first problem.)

    Even if you used LoadLibrary to load the DLL and passed that handle to GetProcAddress, you still crash, because the LoadLibrary notices that the DLL is already loaded and merely increments the reference count.

    #include <windows.h>
    
    typedef HINSTANCE (WINAPI *SXA)(HWND, LPCSTR, LPCSTR,
                                    LPCSTR, LPCSTR, int);
    
    int __cdecl main(int argc, char* argv[])
    {
     if (argc > 1) // set the time bomb
      LoadLibraryEx("shell32.dll", NULL, DONT_RESOLVE_DLL_REFERENCES);
    
     // victim code runs here
     HINSTANCE h = LoadLibrary("shell32.dll");
     if (h) {
      SXA f = (SXA)GetProcAddress(h, "ShellExecuteA");
      if (f) {
       f(NULL, NULL, "notepad.exe", NULL, NULL, SW_SHOWNORMAL);
      }
      FreeLibrary(h);
     }
    }
    

    If you run this program with no command line arguments, then everything works just fine: Notepad is launched without incident. However, if you pass a command line argument, this sets the time bomb, and the call to ShellExecuteA crashes in flames because shell32.dll was loaded without having its DLL references resolved.

    In other words, DONT_RESOLVE_DLL_REFERENCES is fundamentally flawed and should be avoided. It continues to exist solely for backwards compatibility.

  • 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

    Why does DS_SHELLFONT = DS_FIXEDSYS | DS_SETFONT?

    • 23 Comments

    You may have noticed that the numerical value of the DS_SHELLFONT flag is equal to DS_FIXEDSYS | DS_SETFONT.

    #define DS_SETFONT          0x40L   /* User specified font for Dlg controls */
    #define DS_FIXEDSYS         0x0008L
    #define DS_SHELLFONT        (DS_SETFONT | DS_FIXEDSYS)
    

    Surely that isn't a coincidence.

    The value of the DS_SHELLFONT flag was chosen so that older operating systems (Windows 95, 98, NT 4) would accept the flag while nevertheless ignoring it. This allowed people to write a single program that got the "Windows 2000" look when running on Windows 2000 and got the "classic" look when running on older systems. (If you make people have to write two versions of their program, one that runs on all systems and one that runs only on the newer system and looks slightly cooler, they will usually not bother writing the second one.)

    The DS_FIXEDSYS flag met these conditions. Older systems accepted the flag since it was indeed a valid flag, but they also ignored it because the DS_SETFONT flag takes precedence.

    This is one of those backwards-compatibility exercises: How do you design something so that it is possible to write one program that gets the new features on new systems while at the same time degrading gracefully on old systems?

Page 1 of 3 (22 items) 123