Message Only Window

Introduction

A Message Only Window is an invisible window, created with the sole purpose of picking up window messages. There’s very little data associated with it and obviously no painting or similar operations. It’s ideal for simple request-driven systems, where messages could be socket notifications, alerts from other processes, or triggers for inter-thread synchronization. There are many different scenarios where they can come in handy, one of which I’ll write about in an article of its own later.

Creating such a window is nothing out of the ordinary. Register a class with a wndproc and instance handle, create a window granted the class name of the previous step, and the hWndParent set to HWND_MESSAGE: presto.

The scope of this article is not how to register classes or create windows—that’s regarded as a fundamental (yet pretty manageable) requirement. The focus here will be making an efficient object oriented wrap of the Message Only Window concept, along with some best-practice key points and popular idioms. It will also utilize the Thunk32 library, the details of which were described in an earlier article: Thunking in Win32. Be sure to read up on that.

Getting Started

What you’d like to make, to begin with the end, is a set of classes that will allow you to do something along the lines of:

LRESULT onCreate(MessageOnlyWindow* wnd, UINT msg, WPARAM wParam,
                 LPARAM lParam)
{
   std::cout << "WM_CREATE" << std::endl;
   return 0;
}

int main()
{
   MessageOnlyWindow wnd;
   wnd.setMessageHandler(WM_CREATE, onCreate);
   wnd.create();
   wnd.enterMessageLoop();
   return 0;
}

Along with this, you’ll want the completed mechanism to be thread safe, supporting one thread in the message loop, with others making calls to register or remove handlers. If multiple threads try to enter the message loop, only one should be able to do so, at any given time. To get there, you could use critical sections, mutexes, or an array of similar mechanisms. The mutex wrappers in boost, aptly named boost::mutex, wraps this up quite nicely—both with regard to the locking, and a proper cleanup when the objects fall out of scope.

Furthermore, to be able to easily set callbacks to be functions inside classes, as well as global or namespace scope, you’ll be looking at boost::function and boost::bind.

All this baked together, you can come up with a first interface for the main class.

First Interface

class MessageOnlyWindow
{
public:
   typedef boost::function<LRESULT (MessageOnlyWindow*, UINT,
                                    WPARAM, LPARAM)> MESSAGEHANDLER;

   MessageOnlyWindow();
   ~MessageOnlyWindow();
   void create();
   void enterMessageLoop();
   void terminate();
   void setMessageHandler(UINT nMsg, MESSAGEHANDLER func);
   void removeMessageHandler(UINT nMsg);

   inline HWND getHWND() const
   { return m_hWnd; }
   inline LRESULT sendMessage(UINT nMsg, WPARAM wParam,
                              LPARAM lParam) const
   { return ::SendMessage(getHWND(), nMsg, wParam, lParam); }
   inline LRESULT postMessage(UINT nMsg, WPARAM wParam,
                              LPARAM lParam) const
   { return ::PostMessage(getHWND(), nMsg, wParam, lParam); }

private:
   typedef stdext::hash_map<UINT,
      MESSAGEHANDLER> MESSAGESIGNALMAP;
   typedef std::basic_string<TCHAR, std::char_traits<TCHAR>,
      std::allocator<TCHAR> > tstring;
   typedef std::basic_ostringstream<TCHAR, std::char_traits<TCHAR>,
      std::allocator<TCHAR> > tostringstream;

   HWND m_hWnd;
   BOOL m_bLoopRunning;
   BOOL m_bInitialized;
   HANDLE m_eventTerminated;
   boost::mutex m_mutexInitialize;
   boost::mutex m_mutexMessageLoop;
   boost::mutex m_mutexMessageMap;
   MESSAGESIGNALMAP m_mapMessageEvents;
   tstring m_szClassName;

   void cleanup();
   void registerDummyClass(WNDPROC pWndProc);
   void createWindow();
    LRESULT CALLBACK wndProc(HWND hWnd, UINT nMsg, WPARAM wParam,
                             LPARAM lParam);
};

This is not the completed class interface, but a grand place to start. Straight away, you probably spot some unfamiliar constructs, but for the sake of being pragmatic, I’ll begin at the top.

Defining types is a good thing. I’m not one of those who desperately uses typedefs for any type combination in the spectre, but I do tend to use them when templates or function pointers are involved. The most obvious reason to use it is encapsulation. Not in the general sense, because the actual declaration is needed for the compiler to grasp anything, but in a way that hides complexity at points where it doesn’t matter. Once you’ve comprehended that the MESSAGEHANDLER above is a boost::function, describing a functor with return type void, and a reference to the (still) undefined class WindowMessageInfo as sole parameter—there really is no use to keep repeating it. Repeating it in all functions will only clutter the interface, and make for a whole lot of unnecessary work should you want to change it later.

Moving along, let me quickly go through the functions.

  • create: Creates the message only window.
  • enterMessageLoop: Runs a message loop to dispatch incoming window messages. Will not return until terminate has been called, or WM_QUIT is received.
  • terminate: Ends a running message loop.
  • setMessageHandler: Associates a boost functor with the given window message code—overwrites any previously registered handler.
  • removeMessageHandler: Removes a previously registered handler.
  • getHWND: Returns a handle to the internal window.
  • sendMessage: Sends a message to the window, returns immediately.
  • postMessage: Posts a message to the window, and returns as soon as it has been processed.
  • cleanup: Reverses the process of create, and frees any temporaries.
  • registerDummyClass: Registers a window class, to be used by the MOW.
  • createWindow: Self-explaining helper of create.
  • wndProc: Receives messages, and possibly dispatches them to a handler function.

The parameters shipped to each of these functions should be fairly self-explanatory. The only unknown type should be “tstring”, which essentially (as the typedef bear witness to) is a STL string whose internal storage unit will be the unicode friendly wchar_t if the _UNICODE preprocessor definition is specified, and a regular char otherwise. The history behind tostringstream is exactly the same, although it’s never used as a parameter.

Functions that are guaranteed not to alter the class’ instance members are tagged ‘const’, as they should be.

The instance member variables may seem unclear at this point. Why three mutexes? What’s with m_eventTerminated? The usage will be illuminated in good time.

What this interface is clearly lacking, seeing as I mentioned usage of the Thunk32 library earlier on, is namely that—a thunk. You will also need another variable for the sake of creating unique window class names. Why am I not using one window class for all MOWs? Take a second to consider the consequences. When a window class is being registered, one has to supply a wndproc for it to use. You are free to associate another wndproc with a concrete window that implements this class, but there’s no way of doing that prior to calling CreateWindow. Because of this, you would have to implement a single point of entry for the first (few) window messages—for example,through a singleton. Along with the word “singleton” comes a world of potential hurt, far outside the scope of this article. The more apparent of those woes would be to maintain thread safety. It is, however, by no means an impossible task—and in most situations, one common window class would be the way to go. However, the MOW mechanism described here is so lightweight, and so entirely simplistic, that the possible benefit of keeping a low window class count would be closely matched by the cost of extending the design and implementation.

On that note, I’ll head over to the actual implementation. I will not be posting all of the source code here; that would be a whole lot more than necessary. Instead, I’ll link to the completed library for download, at the end of the post.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read