Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
64 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Contents
 Design Principles
 The Window Class
 The Window Class
 and the Application
 Message Pump


 Printable version
 Discuss this article

The Window Class

Windows in Win32 are represented by window handles, so the logical first object to encapsulate is the window handle. Anybody who has tried to wrap window classes knows that you can not initialize the window class with a class method as the window procedure unless it is a static method, so the class also needs a static "message router" that determines which instance of the class is the recipient of the message and calls that instance's window procedure. So how exactly do we determine which instance should receive the message?

Every window has a 32-bit value associated with it intended for use by the application. This value can be set via the SetWindowLong function and retrieved with the GetWindowLong, and this is the core of the message routing technique.

LRESULT CALLBACK Window::MsgRouter(HWND hwnd, UINT message,
                                   WPARAM wparam, LPARAM lparam)
{
  Window *wnd = 0;

  // retrieve associated Window instance
  wnd = reinterpret_cast<Window *>(::GetWindowLong(hwnd, GWL_USERDATA));
  
  // call the windows message handler
  wnd->WndProc(message, wparam, lparam);
}

While this looks perfect, where and when was this value stored? At first I stored it during my Window::Create routine, but I ran into problems. You see, some messages are sent before CreateWindowEx returns - WM_NCCREATE, WM_NCCALCSIZE and WM_CREATE (in that order). If the value does not exist by the time WM_NCCREATE is called then, by some mysterious Windows behavior that I still don't understand, the value never gets inserted. The solution? The WM_NCCREATE message must be handled explicitly within the message router, with the object pointer being stored at this point instead. Okay, but where do we get the value from? The CreateWindowEx function allows the passing of a (void) pointer to window creation data. This data is made available to the window procedure as the lparam parameter in the form of an LPCREATESTRUCT. As an added precaution, we go ahead and store the window handle at this point.

LRESULT CALLBACK Window::MsgRouter(HWND hwnd, UINT message,
                                   WPARAM wparam, LPARAM lparam)
{
  Window *wnd = 0;

  if(message == WM_NCCREATE)
  {
    // retrieve Window instance from window creation data and associate
    wnd = reinterpret_cast<Window *>((LPCREATESTRUCT)lparam)->lpCreateParams;
    ::SetWindowLong(hwnd, GWL_USERDATA, reinterpret_cast<long>(wnd));

	// save window handle
    wnd->SetHWND(hwnd);
  }
  else
    // retrieve associated Window instance
    wnd = reinterpret_cast<Window *>(::GetWindowLong(hwnd, GWL_USERDATA));
  
  // call the windows message handler
  wnd->WndProc(message, wparam, lparam);
}

Message Handling

Alright! We've got our object properly associated with the window handle, and messages being properly routed. But what about that flexibility we mentioned earlier? As it stands, the window procedure has to handle all possible messages - an approach that would require reengineering the class for every new application. Not going to cut it.

In considering how to make this more flexible, I was struct by the fact that MFC and VCL call their methods of associating window messages with message handlers message maps. Being the STL afficionado that I am, that immediately struct a chord with me. How's about I use a std::map to tie a particular message to a particular message handler? That way I could insert and replace a message handler at any time without modifying the class.

For those of you not familar with the Standard Template Library (STL), I heartily recommend SGI's STL Documentation as both an introduction and a reference.

typedef long (* tyMessageHandler)(Window &, HWND, long, long);
typedef std::map<long, tyMessageHandler> tyMessageMap;
typedef tyMessageMap::iterator tyMessageIterator;

The Window class contains a single instance of tyMessageMap. This message map is then searched for the existence of a handler for a given message by the message router, and if none exist the default window procedure is invoked. I also chose to provide two (static) message handlers, partly as a template and partly to provide default functionality.

// Window::GetMessageHandler returns the address of the registered
// message handler if one exists
tyMessageIterator Window::GetMessageHandler(long message)
{
  // m_MsgHandlers is a tyMessageMap instance
  tyMessageIterator it = m_MsgHandlers.find(message);
  if(it = m_MsgHandlers.end())
    return NULL;
  return it;
}

// Window::OnClose is a static method called in response to WM_CLOSE
long Window::OnClose(Window &wnd, HWND hwnd, long param0, long param1)
{
  DestroyWindow(hwnd);
  return 0;
}

// Window::OnDestroy is a static method called in response to WM_DESTROY
long Window::OnDestroy(Window &wnd, HWND hwnd, long param0, long param1)
{
  PostQuitMessage(0);
  return 0;
}

// Final message handler version
LRESULT CALLBACK Window::MsgRouter(HWND hwnd, UINT message,
                                   WPARAM wparam, LPARAM lparam)
{
  Window *wnd = 0;

  if(message == WM_NCCREATE)
  {
    // retrieve Window instance from window creation data and associate
    wnd = reinterpret_cast<Window *>((LPCREATESTRUCT)lparam)->lpCreateParams;
    ::SetWindowLong(hwnd, GWL_USERDATA, reinterpret_cast<long>(wnd));

	// save window handle
    wnd->SetHWND(hwnd);
  }
  else
    // retrieve associated Window instance
    wnd = reinterpret_cast<Window *>(::GetWindowLong(hwnd, GWL_USERDATA));

  if(wnd)
  {
    tyMessageIterator it;
    it = wnd->GetMessageHandler(message);
    if(it != NULL)
      return (it->second)((*wnd), hwnd, wparam, lparam);
  }
  return DefWindowProc(hwnd, message, wparam, lparam);
}

Okay, so the message router takes care of object association, checks to see if there is an appropriate message handler and calls the default window procedure if there isn't. Fine. Now how do you add these message handlers? Behold the Window::RegisterMessageHandler method!

tyMessageHandler Window::RegisterMessageHandler(long message,
                                    tyMessageHandler handler)
{
  tyMessageHandler m = NULL;
  tyMessageIterator it = m_MsgHandlers.find(message);
  if(it != m_MsgHandlers.end())
    m = it->second;
  m_MsgHandlers.insert(std::pair<long,tyMessageHandler>(message, handler));
  return m;
}

Alright, so it wasn't so dramatic. The RegisterMessageHandler method inserts a message handler into the message map and returns the previous message handler, if there was one. That about wraps it up for message handling (I say about because I'll revisit one of the methods described above later). Now let's turn to integrating the Window with the application message pump.





Next : The Window Class and the Application Message Pump