My Photo
Location: Downham Market, Norfolk, United Kingdom

Wednesday, September 26, 2007

I've recently discovered something fascinating about the inner workings of Windows which, whilst known by the cognoscenti, seems not at all well understood and even less well documented.

It is well known that messages posted to a window, e.g. by calling PostMessage, are queued and only get dispatched to the Window Procedure by means of a message loop (or message pump) calling the GetMessage (or PeekMessage with the PM_REMOVE option) and DispatchMessage functions. It is also quite well known that messages sent to a window from the same thread, e.g. by calling SendMessage, are not queued and call the Window Procedure directly. Note that this can easily result in the Window Procedure being called re-entrantly, for example when a message handler itself calls SendMessage. It also means that sent messages are never filtered by the GetMessage wMsgFilterMin and wMsgFilterMax parameters.

But what happens to messages sent to a window from another thread? They neither get queued (in the sense of the posted messages) nor do they call the Window Procedure directly, but instead they are dispatched to the Window Procedure within a GetMessage, PeekMessage or SendMessage function. This dispatch happens invisibly and automatically within the functions; GetMessage and PeekMessage will never return sent messages, but they nevertheless dispatch them!

This knowledge is invaluable for preventing deadlocks. Suppose Thread A receives a message, and within the message handler it waits on some event happening in Thread B. Suppose also that Thread B sends a message to Thread A and does not action the event until the SendMessage function returns. This is a recipe for a deadlock: Thread A is waiting for Thread B's event and Thread B is waiting for Thread A to respond to its message, which it cannot do because it is waiting for Thread B....

With our new-found knowledge the solution is simple. Whilst Thread A is waiting for Thread B's event, it calls PeekMessage. It needn't remove any messages from the queue, nor take any notice of what is returned from PeekMessage, simply calling the function is sufficient! Now the message sent from Thread B is dispatched (within PeekMessage), Thread A responds, and Thread B continues execution and actions the event. Result: no deadlock!