April, 2005

  • The Old New Thing

    Building a dialog template at run-time

    • 21 Comments

    We've spent quite a bit of time over the past year learning about dialog templates and the dialog manager. Now we're going to put the pieces together to do something interesting: Building a dialog template on the fly.

    What we're going to write is an extremely lame version of the MessageBox function. Why bother writing a bad version of something that Windows already does? Because you can use it as a starting point for further enhancements. For example, once you learn how to generate a template dynamically, you can dynamically add buttons beyond the boring "OK" button, or you can add additional controls like a "Repeat this answer for all future occurrences of this dialog" checkbox or maybe insert an animation control.

    I'm going to start with a highly inefficient dialog template class. This is not production-quality, but it's good enough for didactic purposes.

    #include <vector>
    class DialogTemplate {
    public:
     LPCDLGTEMPLATE Template() { return (LPCDLGTEMPLATE)&v[0]; }
     void AlignToDword()
      { if (v.size() % 4) Write(NULL, 4 - (v.size() % 4)); }
     void Write(LPCVOID pvWrite, DWORD cbWrite) {
      v.insert(v.end(), cbWrite, 0);
      if (pvWrite) CopyMemory(&v[v.size() - cbWrite], pvWrite, cbWrite);
     }
     template<typename T> void Write(T t) { Write(&t, sizeof(T)); }
     void WriteString(LPCWSTR psz)
      { Write(psz, (lstrlenW(psz) + 1) * sizeof(WCHAR)); }
    
    private:
     vector<BYTE> v;
    };
    

    I didn't spend much time making this class look pretty because it's not the focus of this article. The DialogTemplate class babysits a vector of bytes to which you can Write data. There is also a little AlignToDword method that pads the buffer to the next DWORD boundary. This'll come in handy, too.

    Our message box will need a dialog procedure which ends the dialog when the IDCANCEL button is pressed. If we had made any enhancements to the dialog template, we would handle them here as well.

    INT_PTR CALLBACK DlgProc(HWND hwnd, UINT wm, WPARAM wParam, LPARAM lParam)
    {
     switch (wm) {
     case WM_INITDIALOG: return TRUE;
     case WM_COMMAND:
      if (GET_WM_COMMAND_ID(wParam, lParam) == IDCANCEL) EndDialog(hwnd, 0);
      break;
     }
     return FALSE;
    }
    

    Finally, we build the template. This is not hard, just tedious. Out of sheer laziness, we make the message box a fixed size. If this were for a real program, we would have measured the text (using ncm.lfCaptionFont and ncm.lfMessageFont) to determine the best size for the message box.

    BOOL FakeMessageBox(HWND hwnd, LPCWSTR pszMessage, LPCWSTR pszTitle)
    {
     BOOL fSuccess = FALSE;
     HDC hdc = GetDC(NULL);
     if (hdc) {
      NONCLIENTMETRICSW ncm = { sizeof(ncm) };
      if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, 0, &ncm, 0)) {
       DialogTemplate tmp;
    
       // Write out the extended dialog template header
       tmp.Write<WORD>(1); // dialog version
       tmp.Write<WORD>(0xFFFF); // extended dialog template
       tmp.Write<DWORD>(0); // help ID
       tmp.Write<DWORD>(0); // extended style
       tmp.Write<DWORD>(WS_CAPTION | WS_SYSMENU | DS_SETFONT | DS_MODALFRAME);
       tmp.Write<WORD>(2); // number of controls
       tmp.Write<WORD>(32); // X
       tmp.Write<WORD>(32); // Y
       tmp.Write<WORD>(200); // width
       tmp.Write<WORD>(80); // height
       tmp.WriteString(L""); // no menu
       tmp.WriteString(L""); // default dialog class
       tmp.WriteString(pszTitle); // title
    
       // Next comes the font description.
       // See text for discussion of fancy formula.
       if (ncm.lfMessageFont.lfHeight < 0) {
         ncm.lfMessageFont.lfHeight = -MulDiv(ncm.lfMessageFont.lfHeight,
                  72, GetDeviceCaps(hdc, LOGPIXELSY));
       }
       tmp.Write<WORD>((WORD)ncm.lfMessageFont.lfHeight); // point
       tmp.Write<WORD>((WORD)ncm.lfMessageFont.lfWeight); // weight
       tmp.Write<BYTE>(ncm.lfMessageFont.lfItalic); // Italic
       tmp.Write<BYTE>(ncm.lfMessageFont.lfCharSet); // CharSet
       tmp.WriteString(ncm.lfMessageFont.lfFaceName);
    
       // Then come the two controls.  First is the static text.
       tmp.AlignToDword();
       tmp.Write<DWORD>(0); // help id
       tmp.Write<DWORD>(0); // window extended style
       tmp.Write<DWORD>(WS_CHILD | WS_VISIBLE); // style
       tmp.Write<WORD>(7); // x
       tmp.Write<WORD>(7); // y
       tmp.Write<WORD>(200-14); // width
       tmp.Write<WORD>(80-7-14-7); // height
       tmp.Write<DWORD>(-1); // control ID
       tmp.Write<DWORD>(0x0082FFFF); // static
       tmp.WriteString(pszMessage); // text
       tmp.Write<WORD>(0); // no extra data
    
       // Second control is the OK button.
       tmp.AlignToDword();
       tmp.Write<DWORD>(0); // help id
       tmp.Write<DWORD>(0); // window extended style
       tmp.Write<DWORD>(WS_CHILD | WS_VISIBLE |
                        WS_GROUP | WS_TABSTOP | BS_DEFPUSHBUTTON); // style
       tmp.Write<WORD>(75); // x
       tmp.Write<WORD>(80-7-14); // y
       tmp.Write<WORD>(50); // width
       tmp.Write<WORD>(14); // height
       tmp.Write<DWORD>(IDCANCEL); // control ID
       tmp.Write<DWORD>(0x0080FFFF); // static
       tmp.WriteString(L"OK"); // text
       tmp.Write<WORD>(0); // no extra data
    
       // Template is ready - go display it.
       fSuccess = DialogBoxIndirect(g_hinst, tmp.Template(),
                                    hwnd, DlgProc) >= 0;
      }
      ReleaseDC(NULL, hdc); // fixed 11 May
     }
     return fSuccess;
    }
    

    The fancy formula for determining the font point size is not that fancy after all. The dialog manager converts the font height from point to pixels via the standard formula:

    fontHeight = -MulDiv(pointSize, GetDeviceCaps(hdc, LOGPIXELSY), 72);
    Therefore, to get the original pixel value back, we need to solve this formula for pointSize so that when it is sent through the formula again, we get the original value back.

    The template itself follows the format we discussed earlier, no surprises.

    One subtlety is that the control identifier for our OK button is IDCANCEL instead of the IDOK you might have expected. That's because this message box has only one button, so we want to let the user hit the ESC key to dismiss it.

    Now all that's left to do is take this function for a little spin.

    void OnChar(HWND hwnd, TCHAR ch, int cRepeat)
    {
     if (ch == TEXT(' ')) {
      FakeMessageBox(hwnd,
       L"This is the text of a dynamically-generated dialog template. "
       L"If Raymond had more time, this dialog would have looked prettier.",
       L"Title of message box");
     }
    }
    
        // add to window procedure
        HANDLE_MSG(hwnd, WM_CHAR, OnChar);
    

    Fire it up, hit the space bar, and observe the faux message box.

    Okay, so it's not very exciting visually, but that wasn't the point. The point is that you now know how to build a dialog template at run-time.

  • The Old New Thing

    Rescuing thread messages from modal loops via message filters

    • 11 Comments

    As we have seen recently, thread messages are eaten by modal loops because they have nowhere to go when dispatched. However, there is a way to see them before they vanish, provided the modal loop is cooperative.

    The WH_MSGFILTER message hook allows you to receive messages passed to the CallMsgFilter function. Fortunately, all the modal loops in the window manager use CallMsgFilter to allow the thread to capture thread messages before they are lost. Therefore, this gives you a way to snoop on messages as they travel through modal loops.

    Let's add a message filter to the program we wrote last time to see how messages pass through a message filter. Note that this is the wrong way to solve the problem. The correct solution was illustrated last time. I'm doing it the wrong way to illustrate message filters since they are not well-understood. (For example, a valid reason for a message filter would to prevent the menu loop from seeing certain input.)

    Start with the program from last the before we changed the PostThreadMessage to a PostMessage, then make the following changes:

    HHOOK g_hhkMSGF;
    
    LRESULT CALLBACK MsgFilterProc(int code, WPARAM wParam, LPARAM lParam)
    {
     MSG* pmsg = (MSG*)lParam;
     if (code >= 0 && IsThreadMessage(pmsg)) return TRUE;
     return CallNextHookEx(g_hhkMSGF, code, wParam, lParam);
    }
    
    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
     g_hhkMSGF = SetWindowsHookEx(WH_MSGFILTER, MsgFilterProc,
        NULL, GetCurrentThreadId());
     if (!g_hhkMSGF) return FALSE;
     DWORD dwThread;
     HANDLE hThread = CreateThread(NULL, 0, ThreadProc,
           UintToPtr(GetCurrentThreadId()), 0, &dwThread);
     ...
    

    Here, we installed a message filter hook on our thread so that we can seem messages as they pass through modal loops. The code parameter tells us what type of modal loop retrieved the message; we ignore it here since we want to do our filtering for all modal loops.

    Run this program and observe that the beeps are no longer lost because our message filter is getting a chance to see them and react to them.

    The message filter trick relies on all modal loops sending the messages they retrieve through a message filter before dispatching them. If you are writing code that is going into a library, and you have a modal loop, then you too should call the message filter before dispatching messages you've retrieved, in case the program using your library wants to do something with the message.

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
     if (!CallMsgFilter(&msg, MSGF_MYLIBRARY)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
     }
    }
    

    The value MSGF_MYLIBRARY is an arbitrary positive value you can choose and document in your library's header file. You can see examples of this in the commctrl.h header file:

    #define MSGF_COMMCTRL_BEGINDRAG     0x4200
    #define MSGF_COMMCTRL_SIZEHEADER    0x4201
    #define MSGF_COMMCTRL_DRAGSELECT    0x4202
    #define MSGF_COMMCTRL_TOOLBARCUST   0x4203
    

    These are the message filters called by the modal loops in the shell common controls library.

    One question you might ask is, "Why use a message filter hook instead of a GetMessage hook?"

    Message filter hooks are less expensive than GetMessage hooks because they are called only upon request, as opposed to a GetMessage hook which is called for every retrieved message. Message filter hooks also tell you which modal loop is doing the filtering, in case you want to adjust your behavior accordingly.

    The downside of message filter hooks is that all modal loops need to remember to call CallMsgFilter as part of their dispatch loop.

  • The Old New Thing

    Watching thread messages disappear

    • 3 Comments

    We saw last time that thread messages are eaten by modal loops. Today we'll illustrate, and then we'll try to fix it next time.

    Start with our scratch program and make the following changes:

    #include <shellapi.h>
    
    BOOL IsThreadMessage(MSG *pmsg)
    {
     if (pmsg->hwnd == NULL) {
      switch (pmsg->message) {
       case WM_APP: MessageBeep(-1); return TRUE;
      }
     }
     return FALSE;
    }
    
    // For illustration, we'll post a thread message every two seconds
    DWORD CALLBACK ThreadProc(void *lpParameter)
    {
     DWORD dwThread = PtrToUint(lpParameter);
     for (;;) {
      Sleep(2000);
      PostThreadMessage(dwThread, WM_APP, 0, 0);
     }
     return 0;
    }
    
    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
     // Start the timer that posts the thread message
     DWORD dwThread;
     HANDLE hThread = CreateThread(NULL, 0, ThreadProc,
           UintToPtr(GetCurrentThreadId()), 0, &dwThread);
     if (!hThread) return FALSE;
     CloseHandle(hThread);
    
     // create some content - just to make things interesting
     g_hwndChild = CreateWindow(WC_LISTVIEW, NULL,
                                WS_CHILD | WS_VISIBLE | LVS_ICON, 0, 0, 0, 0,
                                hwnd, (HMENU)1, g_hinst, 0);
        if (!g_hwndChild) return FALSE;
        SHFILEINFO sfi;
        HIMAGELIST himl = (HIMAGELIST)SHGetFileInfo(TEXT("C:\\"), 0,
                &sfi, sizeof(sfi),
                SHGFI_SYSICONINDEX | SHGFI_DISPLAYNAME | SHGFI_LARGEICON);
        if (!himl) return FALSE;
        ListView_SetImageList(g_hwndChild, himl, LVSIL_NORMAL);
        for (int i = 0; i < 50; i++) {
         LVITEM item;
         item.iItem = i;
         item.iSubItem = 0;
         item.mask = LVIF_TEXT | LVIF_IMAGE;
         item.pszText = sfi.szDisplayName;
         item.iImage = sfi.iIcon;
         if (ListView_InsertItem(g_hwndChild, &item) < 0) return FALSE;
        }
        return TRUE;
    }
    
    void OnClose(HWND hwnd)
    {
     if (MessageBox(hwnd, TEXT("Really?"), TEXT("Title"),
         MB_YESNO) == IDYES) {
      DestroyWindow(hwnd);
     }
    }
    
    // add to window procedure
        HANDLE_MSG(hwnd, WM_CLOSE, OnClose);
    
        while (GetMessage(&msg, NULL, 0, 0)) {
          if (!IsThreadMessage(&msg)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
          }
        }
    

    Run this program and notice that it beeps every two seconds, or at least it does most of the time. If you right-click on the caption bar or grab the edge of the window to start resizing it or grab the scollbar or start a drag-selection or display a message box, the beeps stop. That's because all of those actions are modal operations, and the modal message loop is eating the thread messages.

    Save this program because we'll come back to it.

    The obvious solution is to post the message to the main window itself rather than to the thread. You have a window handle; use it!

    DWORD CALLBACK ThreadProc(void *lpParameter)
    {
     HWND hwnd = reinterpret_cast<HWND>(lpParameter);
     for (;;) {
      Sleep(2000);
      PostMessage(hwnd, WM_APP, 0, 0);
     }
     return 0;
    }
    
     HANDLE hThread = CreateThread(NULL, 0, ThreadProc,
           reinterpret_cast<void*>(hwnd), 0, &dwThread);
    
    // add to window procedure
     case WM_APP: MessageBeep(-1); return 0;
    
        while (GetMessage(&msg, NULL, 0, 0)) {
          if (!IsThreadMessage(&msg)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
          }
        }
    

    Now that that problem has been solved, I'm going to tempt fate and solve the problem the wrong way because I want to illustrate message filters. Next time.

  • The Old New Thing

    If you pull a 1920's fire extinguisher out of the ocean, for pete's sake don't drink it!

    • 5 Comments

    Oceanographer Curtis Ebbesmeyer is a fascinating source of information. He appeared again on my local public radio station to discuss issues related to garbage floating in the ocean. For example, at the 5:05 mark, he reminds us that, if you pull a 1920's fire extinguisher out of the ocean, you shouldn't drink it.

    Other fascinating facts:

    • Ten-pound bowling balls float.
    • Sneakers float upside-down, and the tongue acts as a rudder, resulting in left-footed sneakers and right-footed sneakers washing up on different shores. (Beachcombers from different-footed shores occasionally meet up to try to assemble a pair.)
    • There are 1939 different species of plastic ducks.
  • The Old New Thing

    Thread messages are eaten by modal loops

    • 3 Comments

    Thread messages (as generated by the PostThreadMessage function) do not go anywhere when passed to the DispatchMessage function. This is obvious if you think about it, because there is no window handle associated with a thread message. DispatchMessage has no idea what to do with a message with no associated window. It has no choice but to throw the message away.

    This has dire consequences for threads which enter modal loops, which any thread with a window almost certainly will do at one time or another. Recall that the traditional modal loop looks like this:

    while (GetMessage(&msg, NULL, 0, 0)) {
     TranslateMessage(&msg);
     DispatchMessage(&msg);
    }
    

    If a thread message is returned by the GetMessage function, it will just fall through the TranslateMessage and DispatchMessage without any action being taken. Lost forever.

    Thread messages are generally to be avoided on threads that create windows, for this very reason. Of course, if you're going to create a window, why not use PostMessage instead, passing that window as the target of the posted message? Since there is now a window handle, the DispatchMessage function knows to give the message to your window procedure. Result: Message not lost.

  • The Old New Thing

    Welcome to Millennium Tower, due for completion in May 2005

    • 15 Comments

    The friend who brought to my attention the guide to British pub etiquette reports that Portsmouth's Millennium Tower, centerpiece of the Millennium Project, is still not finished, due for completion (scroll to the end of the article) in May 2005.

    To disguise the embarrassment, they've renamed it to Spinnaker Tower, thereby—as the Hampshire Area Guitar Orchestra describes it—replacing a name that no-one could quite remember how to spell correctly, with a name that no-one can quite remember how to spell correctly".

    If you go back to the original proposal, construction was to begin in Winter 1998 with completion in Autumn 1999. In reality, the project was so badly delayed that construction didn't even begin until 2003 (I believe). Quite an embarrassment to what was supposed to be "Millennium City". (At least it's no longer home to the Tricorn Carpark and Shopping Centre, named Britain's ugliest building.)

    If you go to the project's home page, you can catch up on everything that's going on, or at least pretend to, because the "News Update" and "The Projects" links are both 404.

    But that's okay, because there'll be "More Deatils Soon".

  • The Old New Thing

    What is the HINSTANCE passed to SetWindowsHookEx used for?

    • 20 Comments

    The SetWindowsHookEx function accepts a HINSTANCE parameter. The documentation explains that it is a handle to the DLL containing the hook procedure. Why does the window manager need to have this handle?

    It needs the handle so it knows which DLL to load into each process when the hook fires. It injects the DLL you pass, then calls the function you pass. Clearly the function needs to reside in the DLL you pass in order for its code to be there when the window manager calls it.

    This is also why hook functions must reside in DLLs. You can't load an EXE into another EXE.

    The WH_KEYBOARD_LL and WH_MOUSE_LL hooks are exceptions to this rule. These two are non-injecting hooks, as explained in their respective documentation pages. Rather, the hook function is called in its original thread context.

    Okay, armed with this information, perhaps you can solve this person's problem with global hooks.

  • The Old New Thing

    The new scratch program

    • 49 Comments

    I think it's time to update the scratch program we've been using for the past year. I hear there's this new language called C++ that's going to become really popular any day now, so let's hop on the bandwagon!

    #define STRICT
    #define UNICODE
    #define _UNICODE
    #include <windows.h>
    #include <windowsx.h>
    #include <ole2.h>
    #include <commctrl.h>
    #include <shlwapi.h>
    #include <shlobj.h>
    #include <shellapi.h>
    
    HINSTANCE g_hinst;
    
    class Window
    {
    public:
     HWND GetHWND() { return m_hwnd; }
    protected:
     virtual LRESULT HandleMessage(
                             UINT uMsg, WPARAM wParam, LPARAM lParam);
     virtual void PaintContent(PAINTSTRUCT *pps) { }
     virtual LPCTSTR ClassName() = 0;
     virtual BOOL WinRegisterClass(WNDCLASS *pwc)
         { return RegisterClass(pwc); }
     virtual ~Window() { }
    
     HWND WinCreateWindow(DWORD dwExStyle, LPCTSTR pszName,
           DWORD dwStyle, int x, int y, int cx, int cy,
           HWND hwndParent, HMENU hmenu)
     {
      Register();
      return CreateWindowEx(dwExStyle, ClassName(), pszName, dwStyle,
                      x, y, cx, cy, hwndParent, hmenu, g_hinst, this);
     }
    private:
     void Register();
     void OnPaint();
     void OnPrintClient(HDC hdc);
     static LRESULT CALLBACK s_WndProc(HWND hwnd,
         UINT uMsg, WPARAM wParam, LPARAM lParam);
    protected:
     HWND m_hwnd;
    };
    
    void Window::Register()
    {
        WNDCLASS wc;
        wc.style         = 0;
        wc.lpfnWndProc   = Window::s_WndProc;
        wc.cbClsExtra    = 0;
        wc.cbWndExtra    = 0;
        wc.hInstance     = g_hinst;
        wc.hIcon         = NULL;
        wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName  = NULL;
        wc.lpszClassName = ClassName();
    
        WinRegisterClass(&wc);
    }
    
    LRESULT CALLBACK Window::s_WndProc(
                   HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
     Window *self;
     if (uMsg == WM_NCCREATE) {
      LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
      self = reinterpret_cast<Window *>(lpcs->lpCreateParams);
      self->m_hwnd = hwnd;
      SetWindowLongPtr(hwnd, GWLP_USERDATA,
                reinterpret_cast<LPARAM>(self));
     } else {
      self = reinterpret_cast<Window *>
                (GetWindowLongPtr(hwnd, GWLP_USERDATA));
     }
     if (self) {
      return self->HandleMessage(uMsg, wParam, lParam);
     } else {
      return DefWindowProc(hwnd, uMsg, wParam, lParam);
     }
    }
    
    LRESULT Window::HandleMessage(
                              UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
     LRESULT lres;
    
     switch (uMsg) {
     case WM_NCDESTROY:
      lres = DefWindowProc(m_hwnd, uMsg, wParam, lParam);
      SetWindowLongPtr(m_hwnd, GWLP_USERDATA, 0);
      delete this;
      return lres;
    
     case WM_PAINT:
      OnPaint();
      return 0;
    
     case WM_PRINTCLIENT:
      OnPrintClient(reinterpret_cast<HDC>(wParam));
      return 0;
     }
    
     return DefWindowProc(m_hwnd, uMsg, wParam, lParam);
    }
    
    void Window::OnPaint()
    {
     PAINTSTRUCT ps;
     BeginPaint(m_hwnd, &ps);
     PaintContent(&ps);
     EndPaint(m_hwnd, &ps);
    }
    
    void Window::OnPrintClient(HDC hdc)
    {
     PAINTSTRUCT ps;
     ps.hdc = hdc;
     GetClientRect(m_hwnd, &ps.rcPaint);
     PaintContent(&ps);
    }
    
    class RootWindow : public Window
    {
    public:
     virtual LPCTSTR ClassName() { return TEXT("Scratch"); }
     static RootWindow *Create();
    protected:
     LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
     LRESULT OnCreate();
    private:
     HWND m_hwndChild;
    };
    
    LRESULT RootWindow::OnCreate()
    {
     return 0;
    }
    
    LRESULT RootWindow::HandleMessage(
                              UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
     switch (uMsg) {
      case WM_CREATE:
       return OnCreate();  
    
      case WM_NCDESTROY:
       // Death of the root window ends the thread
       PostQuitMessage(0);
       break;
    
      case WM_SIZE:
       if (m_hwndChild) {
        SetWindowPos(m_hwndChild, NULL, 0, 0,
                     GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam),
                     SWP_NOZORDER | SWP_NOACTIVATE);
       }
       return 0;
    
      case WM_SETFOCUS:
       if (m_hwndChild) {
        SetFocus(m_hwndChild);
       }
       return 0;
     }
    
     return __super::HandleMessage(uMsg, wParam, lParam);
    }
    
    RootWindow *RootWindow::Create()
    {
     RootWindow *self = new RootWindow();
     if (self && self->WinCreateWindow(0,
           TEXT("Scratch"), WS_OVERLAPPEDWINDOW,
           CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
           NULL, NULL)) {
          return self;
      }
     delete self;
     return NULL;
    }
    
    int PASCAL
    WinMain(HINSTANCE hinst, HINSTANCE, LPSTR, int nShowCmd)
    {
     g_hinst = hinst;
    
     if (SUCCEEDED(CoInitialize(NULL))) {
      InitCommonControls();
    
      RootWindow *prw = RootWindow::Create();
      if (prw) {
       ShowWindow(prw->GetHWND(), nShowCmd);
       MSG msg;
       while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
       }
      }
      CoUninitialize();
     }
     return 0;
    }
    

    The basic idea of this program is the same as our old scratch program, but now it has that fresh lemony C++ scent. Instead of keeping our state in globals, we declare a C++ class and hook it up to the window. For simplicity, the object's lifetime is tied to the window itself.

    First, there is a bare-bones Window class which we will use as our base class for any future "class associated with a window" work. The only derived class for now is the RootWindow, the top-level frame window that for now is the only window that the program uses. As you may suspect, we may have other derived classes later as the need arises.

    The reason why the WinRegisterClass method is virtual (and doesn't do anything interesting) is so that a derived class can modify the WNDCLASS that is used when the class is registered. I don't have any immediate need for it, but it'll be there if I need it.

    We use the GWLP_USERDATA window long to store the pointer to the associated class, thereby allowing us to recover the object from the window handle.

    Observe that in the RootWindow::HandleMessage method, I used the Visual C++ __super extension. If you don't want to rely on a nonstandard extension, you can instead write

    class RootWindow : public Window
    {
    public:
     typedef Window super;
     ...
    

    and use super instead of __super.

    This program doesn't do anything interesting; it's just going to be a framework for future samples.

  • The Old New Thing

    Sweden's Worst Driver

    • 19 Comments

    Swedish Television announced a new reality-based television program called Sweden's Worst Driver (Sveriges värsta bilförare). I've done a bad job of translating some excerpts:

    TV4 has gathered seven of Sweden's worst drivers and subjects them to a series of tests and challenges. Those who complete each exercise the best get a prize and leave the contest.

    Thus, whoever is the last one left in the television series, which begins on the 29th of April, "wins" and is the worst at the same time.

    ... "That there exist people who aren't so good at driving, I knew that already. But people as bad as this I didn't think existed," says [race driver and judge Richard Göransson].

    ... Sven-Ingvar "Snappe" Eriksson, 39, from Hammerdal in Jämtland was nominated by his wife Lena because he has crashed his car at least 25 to 30 times since he got his driver's license 22 years ago.

    Participants get to do several exercises, among them, yielding the right of way for unexpected traffic on forest roads, backing up with a trailer, parking in a tight spot on a hill, and getting out of a parking garage.

    This is sort of in the spirit of those makeover-type programs like What Not To Wear, *** Eye for the Straight Guy, where you ridicule somebody for their general lameness. But at least in those shows, after they make fun of you, they then try to help you be better! Even in the dreadful Queen for a Day, the humiliated "winner" at least got a prize. No such luck, it seems, for Sweden's worst driver.

    It's now a simple matter of time before this show reaches the United States.

  • The Old New Thing

    The Itanium's so-called stack

    • 6 Comments

    Last year I alluded to the fact that the Itanium processor has two stacks. The one that is traditionally thought of as "the stack" (and the one that the sp register refers to) is a manually managed block of memory from which a function can carve out space to use during its execution. For example, if you declare a local variable like

    TCHAR szBuffer[MAX_PATH];
    

    then that buffer will go on "the stack".

    But not all local variables are on "the stack".

    Recall that the Itanium has a very large number of registers, most of which participate in function calls. Consequently, many local variables are placed into registers rather than "the stack", and when a function is called, those registers are "squirreled away" by the processor and "unsquirreled" when the function returns. Where do they get squirreled? Well, the processor can often just squirrel them into other unused registers through a mechanism I won't go into. (Those still interested can read Intel's documents on the subject.) If the processor runs out of squirrel-space, it spills them into main memory, into a place known as the "register backing store". This is another stack-like chunk of memory separate from "the stack". (Here's Slava Oks artistic impression of the layout of the ia64's stacks.)

    As already noted, one consequence of this dual-stack model is that a stack buffer overflow will not corrupt the return address, because the return address is not kept on "the stack"; rather, it is kept in the "squirrel space" or (in the case of spillage) in the register backing store.

    Another consequence of this dual-stack model is that various tricks to locate the start of the stack will find only one of the stacks. Missing out on the other stack will cause problems if you think grovelling "the" stack will find all accessible object references.

    The Itanium architecture challenges many assumptions and is much less forgiving of various technically-illegal-but-nobody-really-enforced-it-before shenanigans, some of which I have discussed in earlier entries. To this list, add the "second stack".

Page 1 of 3 (26 items) 123