Processing Windows Messages Using MsgWaitForMultipleObjects

Credit: Michael Robin

Problem

In a Win32 application, you need to process messages, but you also want to wait for kernel-level waitable objects and coordinate several activities.

Solution

A Windows application message loop, also known as its message pump, is at the heart of Windows. It’s worth some effort to ensure that the heart beats properly and regularly:

import win32event
import pythoncom

TIMEOUT = 200 # ms

StopEvent = win32event.CreateEvent(None, 0, 0, None)
OtherEvent = win32event.CreateEvent(None, 0, 0, None)

class myCoolApp:
    def OnQuit(self):
          if areYouSure(  ):
              win32event.SetEvent(StopEvent) # Exit msg pump

def _MessagePump( ):
     waitables = StopEvent, OtherEvent
     while 1:
         rc = win32event.MsgWaitForMultipleObjects(
             waitables,
             0, # Wait for all = false, so it waits for anyone
             TIMEOUT, # (or win32event.INFINITE)
             win32event.QS_ALLEVENTS) # Accepts all input

         # You can call a function here, if it doesn't take too long. It will
         # be executed at least every 200ms -- possibly a lot more often,
         # depending on the number of Windows messages received.

         if rc == win32event.WAIT_OBJECT_0:
             # Our first event listed, the StopEvent, was triggered, so we must exit
             break
         elif rc == win32event.WAIT_OBJECT_0+1:
             # Our second event listed, "OtherEvent", was set. Do whatever needs
             # to be done -- you can wait on as many kernel-waitable objects as
             # needed (events, locks, processes, threads, notifications, and so on).
             pass
         elif rc == win32event.WAIT_OBJECT_0+len(waitables):
             # A windows message is waiting - take care of it. (Don't ask me
             # why a WAIT_OBJECT_MSG isn't defined < WAIT_OBJECT_0...!).
             # This message-serving MUST be done for COM, DDE, and other
             # Windowsy things to work properly!
             if pythoncom.PumpWaitingMessages(  ):
                  break # we received a wm_quit message
         elif rc == win32event.WAIT_TIMEOUT:
             # Our timeout has elapsed.
             # Do some work here (e.g, poll something you can't thread)
             # or just feel good to be alive.
             pass
         else:
             raise RuntimeError("unexpected win32wait return value")

Discussion

Most Win32 applications must process messages, but often you want to wait on kernel waitables and coordinate a lot of things going on at the same time. A good message pump structure is the key to this, and this recipe exemplifies a reasonably simple but effective one.

Messages and other events will be dispatched as soon as they are posted, and a timeout allows you to poll other components. You may need to poll if the proper calls or event objects are not exposed in your Win32 event loop, as many components insist on running on the application’s main thread and cannot run on spawned threads.

You can add many other refinements, just as you can to any other Win32 message-pump approach. Python lets you do this with as much precision as C does. But the relatively simple message pump in the recipe is already a big step up from the typical naive application that can either serve its message loop or wait on kernel waitables, but not both.

The key to this recipe is the Windows API call MsgWaitForMultipleObjects, which takes several parameters. The first is a tuple of kernel objects you want to wait for. The second parameter is a flag that is normally 0; 1 indicates that you should wait until all the kernel objects in the first parameter are signaled, although you almost invariably want to stop waiting when any one of these objects is signaled. The third is a flag that specifies which Windows messages you want to interrupt the wait; always pass win32event.QS_ALLEVENTS here to make sure any Windows message interrupts the wait. The fourth parameter is a timeout period (in milliseconds), or win32event.INFINITE if you are sure you do not need to do any periodic polling.

This function is a polling loop and, sure enough, it loops (with a while 1:, which is terminated only by a break within it). At each leg of the loop, it calls the API that waits for multiple objects. When that API stops waiting, it returns a code that explains why it stopped waiting. A value of win32event.WAIT_OBJECT_0 to win32event.WAIT_OBJECT_0+N-1 (in which N is the number of waitable kernel objects in the tuple you passed as the first parameter) means that the wait finished because one of those objects was signaled (which means different things for each kind of waitable kernel object). The return’s code difference from win32event.WAIT_OBJECT_0 is the index of the relevant object in the tuple. win32event.WAIT_OBJECT_0+N means that the wait finished because a message was pending, and in this case our recipe processes all pending Windows messages via a call to pythoncom.PumpWaitingMessages. This function returns true if a WM_QUIT message was received, so in this case we break out of the whole while loop. A code of win32event.WAIT_TIMEOUT means the wait finished because of a timeout, so we can do our polling there. In this case, no message is waiting, and none of our kernel objects of interest were signaled.

Basically, the way to tune this recipe for yourself is by using the right kernel objects as waitables (with an appropriate response to each) and by doing whatever you need to do periodically in the polling case. While this means you must have some detailed understanding of Win32, of course, it’s still quite a bit easier than designing your own special-purpose, message-loop function from scratch.

See Also

Documentation for the Win32 API in win32all (http://starship.python.net/crew/mhammond/win32/Downloads.html) or ActivePython (http://www.activestate.com/ActivePython/); Windows API documentation available from Microsoft (http://msdn.microsoft.com); Python Programming on Win32, by Mark Hammond and Andy Robinson (O’Reilly, 2000).

Get Python Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.