If you're making class wrappers for a "message"-based windowing API (Win32, for example), how do you route messages to the correct class instance?

Here's the way I did it in C++ (MFC is similar; I haven't grovelled over the VCL source enough to be certain, but I suspect they're in the same ballpark):

A program gets "messages" through callback functions, which must be C functions. A static member function has no "this pointer", so it'll do just fine. In Win32, this callback is generically referred to as a WindowProc, though you can name yours whatever you like because you're just passing somebody a pointer to it. Here's what it looks like in the header file (with many lines of clutter removed):

class wndbase {
private:
    static std::map< HWND, wndbase * >  _instmap;

    static LRESULT          call_instance_wnd_proc( HWND hwnd, UINT msg, 
                                                    WPARAM wparam, 
                                                    LPARAM lparam );

    static LRESULT CALLBACK static_wnd_proc( HWND hwnd, UINT msg, 
                                             WPARAM wparam, LPARAM lparam );

protected:
    //  Instance member which handles messages.
    virtual LRESULT         wnd_proc( UINT msg, WPARAM wparam, 
                                      LPARAM lparam );

    //  Add and remove instances from/to _instmap
    static void             add_instance( wndbase *wnd );
    static void             remove_instance( HWND hwnd );
};

Okay. So static_wnd_proc is our static WindowProc member. It can call any of the static member functions, and it can access _instmap. wind_proc is the function that the instances use to do something useful with the messages as they come in. We've given Windows a pointer to static_wnd_proc, and told it that this is the guy to talk to when you want to talk to our window.

Our problem is to take the messages that static_wnd_proc receives, and pass them to the wind_proc member of the appropriate instance. Here's how we do it: Each wndbase instance is associated with a unique window, which means a unique HWND value; that's how we identify a window to the Win32 C API. We can use that ourselves. _instmap is an associative array: For each instance of class wndbase which is currently associated with a real window, there is an entry in _instmap. When static_wnd_proc gets a message, first checks to see if it's one of a couple of messages (create and destroy, mainly) which get special handling. If it's not one of those, it calls call_instance_wnd_proc and passes it the HWND of the window the message refers to, along with all the particulars of the message. call_instance_wnd_proc goes to _instmap and gets a pointer to the relevant wndbase instance. If there is a relevant instance, it calls that instances wnd_proc member through the pointer. Each instance already knows what its HWND is (add_instance made sure of that when it was called to add the class instance to _instmap), so we don't bother passing an HWND there.

Here's an example: When the user clicks the left mouse button on a given window, the operating system1 will call static_wnd_proc with the following arguments:

  HWND    hwnd                An arbitrary 16-bit integer
  UINT    msg                 An integer constant named WM_LBUTTONDOWN
  WPARAM  wparam              Flags specifying keyboard state
  LPARAM  lparam (low word)   Horizontal mouse pointer position
  LPARAM  lparam (high word)  Vertical mouse pointer position

The mouse position is given in client coordinates, {0, 0} being the top left corner of what Windows calls the "client area" of the window. static_wnd_proc will call call_instance_wnd_proc, which digs up the appropriate wndbase instance and calls p->wnd_proc( msg, wparam, lparam );. Since wndbase::wnd_proc() is virtual, any number of subclasses can do whatever they like from there on in.

Other messages have different names, and they use wparam and lparam in different ways; there is much casting of long ints to struct pointers.

I've simplified this to some extent: Dialogs in Win32 are somewhat different animals from your ordinary window, and common control subclasses handle a few things differently, too. Most of the time, the same or very similar things are happening in all three cases.

I can already see whizkid's eyes bugging out in horror as he reads this: We've got some overhead here, no joke. But overhead isn't an absolute unmitigated evil in all cases. Sometimes you can afford it. I've written utilities with the above code which I use every day, and it's not a problem: This is just the user interface. Time critical code should never be using the message mechanism at all, if it can possibly be helped: Even without an extra layer like this, it's still a mess.


1 Win32, remember: Everything's in the kernel as long as it doesn't belong there.