I’ve got a message for you!

We were talking at work about the windows message loop and I remembered that Raymond Chen’s book covers this in more detail than I have ever seen elsewhere. I thought I’d take the opportunity to type up my understanding, mainly as a means of checking that I do understand this a little. This was all provoked by a discussion of .NET SynchronizationContexts which I’ll mention at the end.
 
As we know, Windows messages can be directed at other windows via a SendMessage or a PostMessage. Well, there are a few other API calls but we’ll ignore them as they fit into the model in the obvious way. In Win32 land, the usual description is that SendMessage gets the message to the window directly by calling the window procedure whereas PostMessage puts the message into the message queue of the thread responsible for the target window. This thread polls for messages in its message loop, GetMessage, and then dispatches the message to the window procedure, DispatchMessage, after checking for things like accelerators, TranslateAccelerator, and after giving inbuilt utilities like the dialog manager a chance to do something with the message, IsDialogMessage.
 
The problem with this description is that it does not explain how a SendMessage causes the target thread to be interrupted in order for the window procedure to get its direct call. The answer of course is that it doesn’t. All cross-thread message queue interaction in Windows happen in a cooperative fashion. It just wouldn’t be safe to interrupt the target thread at an arbitary point and there is no mechanism for describing safe points. Chen’s book gives the key insight for understanding this. What we refer to as the message queue is in fact an object that contains three queues.
 
One queue is for sent messages that are targeted for a window handled by a thread which isn’t the current thread. If we are sending a message to a window that is managed by the thread that we are on, we can simply call the window procedure directly. If the send is crossing a thread boundary, we have no means
to interrupt the target thread so we place the message in a queue for that thread to handle at some safe point in the future. We then wait for the response. The second queue is for posted messages, which we can fire and forget, and the third queue is for input events.
 
SendMessage messages can be dispatched to the window procedure at three points. If the target thread is blocked inside a cross-thread SendMessage itself, waiting for the reply, the message it is sent will hijack the thread and call the window procedure. Calls to PeekMessage and GetMessage also check the SendMessage queue before doing anything else and dispatch all messages in the queue directly to the window procedures. By this means, SendMessage messages overtake posted messages that may have been in the queue for some time.
 
Posted messages and input messages are handled inside PeekMessage. GetMessage is essentially a call to PeekMessage which passes relevant flags that cause no filtering on the message type and which cause  the message to be removed after it is retrieved. It is PeekMessage which we need to understand to explain
how special messages such as WM_QUIT, WM_PAINT, WM_MOUSEMOVE and WM_TIMER are handled.
 
PeekMessage first dispatches anything in the SendMessage queue to the relevant window procedure. It then tries to find a message in the PostMessage queue. If something suitable is present in this queue it returns it.
If a QUIT is pending, determined by checking a thread flag, the flag is cleared, a WM_QUIT message is generated and returned.
 
If the mouse has moved and a mouse move event is included in the filter a WM_MOUSEMOVE is added to the input queue, but we do not return just yet.
 
Next the input queue is checked for a suitable message which is returned if it is present. We may remove this message depending on the flags.
 
If there are pending paints and a WM_PAINT would satisfy the filter, then a WM_PAINT is generated and  returned. Note that the paint is never added to a queue.
 
Lastly we check for elapsed timers, and should any satisfy the filter, events are added to the input queue and returned. We may remove the message depending on the flags.
Note that when we return a message that was added to the input queue, we may not remove it . This will depend on whether the flags passed to PeekMessage specify that the removal should happen. This means that there are situations when the input queue can be filled by messages that were generated by calls to PeekMessage.
 
Now on to SynchronizationContext. This allows threads to be marked as having a means for having operations executed upon them. The builtin implementation executes calls on the ThreadPool, but one can define subclasses of this class and associate them with threads. Several implementations are around in the .NET framework, in particular for windows forms and WPF. When an instance of a windows forms object is made on a thread, the synchronisation context of the thread is set to be an instance of the WindowsFormsSynchronizationContext. In this case the Post and Send operations hook into the mechanisms detailed above, using PostMessage and SendMessage and a windows event loop to get the operation to happen on the relevent thread at a suitable safe point.
 
Synchronization contexts are also a means for getting the CLR to call back into the context object when either a wait is called on the thread or when an asynchronous operation is started allowing the context to track the number of outstanding operations. There is good coverage of this in Joe Duffy’s book.
 
Advertisements
This entry was posted in Computers and Internet. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s