WindowLib Message Cracking

The WindowLib library uses a message-cracking pattern that employs C++ templates rather than preprocessor macros. I created a set of Handler templates that each implement a Handle method that accepts a WPARAM and an LPARAM. Each template is specialized for a particular window message and also declares a pure virtual method that is unique to the message.

Here's a description of message cracking and how I approached the task for WindowLib.

What Is Message Cracking?

In the Win32 API, messages are used for initiating tasks and passing information in the system. For example, a message may be sent to a window with a SendMessage function call to notify it of a change in its size:

SendMessage(hWnd, WM_SIZE, static_cast<WPARAM>(state), MAKELPARAM(cx, cy));

(Note: Typically the system itself sends the WM_SIZE message when appropriate.)

The state, cx, and cy variables are all parameters that provide additional information about the WM_SIZE message. These are "packaged" into two parameters that are sent with every Windows message: WPARAM and LPARAM.

A window processes its messages in a special function called a window procedure. This procedure often contains a large switch statement that catches the messages the window is interested in and "cracks," or parses, their parameters out of the WPARAM and LPARAM message parameters.

LRESULT CALLBACK WndProc(
   HWND hWnd, 
   UINT message, 
   WPARAM wParam, 
   LPARAM lParam)
{
    switch (message)
    {
    case WM_SIZE:
        {
        UINT state = static_cast<UINT>(wParam);
        int cx = LOWORD(lParam);
        int cy = HIWORD(lParam);

        // do something here with state, cx, and cy.

        return 0;
        }

    default:
        break;
    }

    return DefWindowProc(hWnd, messsage, wParam, lParam);
}

This doesn't look too bad, but Windows applications written this way can become complex rather quickly as the number of messages being handled increases, and the size of the switch begins to grow. Also, extracting the values from wParam and lParam is rather tedious and can be a source of errors if the developer isn't careful.

To make the job a little easer, a header file named windowsx.h is provided with the Win32 API. This header defines several macros called message crackers that simplify the packing and extraction of message parameters.

#define HANDLE_MSG(hwnd, message, fn)    \
    case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))

// ...

/* void Cls_OnSize(HWND hwnd, UINT state, int cx, int cy) */
#define HANDLE_WM_SIZE(hwnd, wParam, lParam, fn) \
    ((fn)((hwnd), (UINT)(wParam), (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam)), 0L)
#define FORWARD_WM_SIZE(hwnd, state, cx, cy, fn) \
    (void)(fn)((hwnd), WM_SIZE, (WPARAM)(UINT)(state), MAKELPARAM((cx), (cy)))

Using these macros, the above switch statement could be simplified.

void MyWindow_OnSize(HWND hwnd, UINT state, int cx, int cy)
{
    // do something here with state, cx, and cy.
}

LRESULT CALLBACK WndProc(
   HWND hWnd, 
   UINT message, 
   WPARAM wParam, 
   LPARAM lParam)
{
    switch (message)
    {
        HANDLE_MSG(hWnd, message, MyWindow_OnSize);

    default:
        break;
    }

    return DefWindowProc(hWnd, messsage, wParam, lParam);
}

This is simpler, but it's not a panacea. Macros do not provide type-safety, and when used in this manner they tend to obfuscate normal C/C++ syntax. A switch statement is supposed to contain case blocks, yet the one shown above doesn't appear to contain any.

MFC has its own mechanism for handling and dispatching messages, which I won't go into here, but it relies upon an even more elaborate system of macros. For an excellent description and analysis of the mechanism I refer the reader to the book MFC Internals by George Shepherd and Scott Wingo.

My Approach

Rather than rely upon the preprocessor, I decided to look for a template-based method of "cracking" Win32 message parameters. I created a header file of abstract template classes that define type-safe message-cracking methods. Each of these classes also defines a pure virtual method that must be implemented in a derived class.

For example, Handle<WM_SIZE> declares the pure virtual method void OnSize(UINT state, int cx, int cy). The parameters to this method represent the values that are packaged in WPARAM and LPARAM when the message is sent. The Handle method performs the appropriate casts before calling the virtual method.

typedef LONG (__stdcall * forwardFn_t)(HWND, UINT, WPARAM, LPARAM);

template<UINT msg> class Handler;

// ...

template<> class Handler<WM_SIZE> 
{
protected:
    virtual void OnSize(UINT state, int cx, int cy) = 0;

public:
    LRESULT Handle(WPARAM wParam, LPARAM lParam)
    {
        OnSize(static_cast<UINT>(wParam), LoWORD(lParam), HiWORD(lParam));
        return 0L;
    }

    static void Forward(HWND hWnd, forwardFn_t pfn, UINT state, int cx, int cy)
    {
        (*pfn)(hWnd, msg, static_cast<WPARAM>(state), MAKELPARAM(cx, cy));
    }
};

The class also provides a static Forward function that may be used to send a WM_SIZE message without having to pack the parameters into WPARAM and LPARAM parameters. It accepts a window handle, a pointer to a function that matches the typical Win32 message-sending function pattern (as defined in forwardFn_t), and the parameters to the WM_SIZE message.

Using the Handlers

For a class derived from Window to handle a message, it derives from the Handler specialization for that message and implements its pure virtual method. Then, in its WindowProc implementation, it calls the Handle method for the appropriate template specialization, as follows:

class MyWindow : 
	public Window, 
	public Handler<WM_SIZE> // The MyWindow class must now implement a WM_SIZE handler
{
public:
   MyWindow(HWND initParent = NULL) : 
      Window(
         _T("MY_SAMPLE_WINDOW"), 
         _T(""),
         initParent
         )
      {
      }

   // This is the implementation of the pure virtual in Handler<WM_SIZE>
   virtual void OnSize(UINT state, int cx, int cy)
   {
      // do something here with state, cx, and cy.
   }

   LRESULT CALLBACK WindowProc(
      UINT message, 
      WPARAM wParam, 
      LPARAM lParam
      )
   {
      switch (message) 
      {   
      // This will call the OnSize() method above, safely extracting
      // the parameters from wParam and lParam.
      case WM_SIZE:
         return Handler<WM_SIZE>::Handle(wParam, lParam);

      default: 
         return Window::WindowProc(message, wParam, lParam); 
      } 
   }
};

Because the Handler<WM_SIZE>::Handle method will correctly cast the WPARAM and LPARAM parameters when calling MyWindow's OnSize implementation, the developer is freed from worrying about correctly casting the WPARAM and LPARAM variables properly. Even better, since this message handling method does not rely on the preprocessor like the message-cracking macros in windowsx.h do, there is stronger type checking at compile time and easier debugging when something does go wrong.

Some purists may balk at the use of multiple inheritance here, but it's really a mixin pattern. The Handler specializations are specifying a handler method that the derived class must implement, as well as providing the correct override of the Handle method to call the virtual handler properly.

I considered getting rid of the WindowProc case statement by doing the message dispatching down in the bowels of the Window class. I decided against this: no implementation I could find could be done without the preprocessor and still provide acceptable performance and clarity.

Making Your Own Crackers

You can't write Win32 applications for very long without creating your own messages. Rather than leaving the comfort of the message-cracking templates you can use them to create your own. For example, in the WindowLib "Hello, World" example you'll find the following declarations in Baseline.h:

enum
{
   // Notify the client area when text is changed.
   WM_BASELINE_REDRAW = WM_APP,

   // Notify the application when the "Set Text" dialog has closed.
   WM_BASELINE_CLOSE_SETTEXT
};

template<UINT msg> class Handler : 
    public ParksComputing::WindowLib::Handler<msg>
{
};

// Handler prototype for redraw message.
template<> class Handler<WM_BASELINE_REDRAW>
{
protected:
    virtual void OnBaselineRedraw() = 0;
    
public:
    LRESULT Handle(WPARAM wParam, LPARAM lParam)
    {
        OnBaselineRedraw();
        return 0L;
    }
    
    static void Forward(HWND hWnd, forwardFn_t pfn)
    {
        (*pfn)(hWnd, msg, 0L, 0L);
    }
};


// Handler prototype for close-and-set-text message.
template<> class Handler<WM_BASELINE_CLOSE_SETTEXT>
{
protected:
    virtual void OnBaselineCloseSetText(HWND hwndDlgHandle) = 0;
    
public:
    LRESULT Handle(WPARAM wParam, LPARAM lParam)
    {
        OnBaselineCloseSetText(reinterpret_cast<HWND>(lParam));
        return 0L;
    }
    
    static void Forward(HWND hWnd, forwardFn_t pfn, HWND hwndDlgHandle)
    {
        (*pfn)(hWnd, msg, 0L, 
            reinterpret_cast<LPARAM>(hwndDlgHandle));
    }
};

Now the main application can derive from these specializations and implement their handlers. Also, the Forward methods make it easier to send these messages to other windows.