Recipe9.10.Processing Windows Messages Using MsgWaitForMultipleObjects


Recipe 9.10. 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's 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(object):     def OnQuit(self):           # assume 'areYouSure' is a global function that makes a final           # check via a message box, a fancy dialog, or whatever else!           if areYouSure( ):               win32event.SetEvent(StopEvent) # Exit msg pump def _MessagePump( ):      waitables = StopEvent, OtherEvent      while True:          rc = win32event.MsgWaitForMultipleObjects(              waitables,              ,       # Wait for all = false, so it waits for any one              TIMEOUT, # (or win32event.INFINITE)              win32event.QS_ALLEVENTS) # Accept all kinds of events          # You can call a function here, if it doesn't take too long. It will          # be executed at least every TIMEOUT ms -- 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, terminating the message pump              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              # Windows-y 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 you often 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 pretty effective one.

With the message pump shown in this recipe, messages and other events get 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 only on the application's main thread and cannot run on spawned (secondary) 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, thanks to Mark Hammond's PyWin32 package (which used to be known as win32all). However, the relatively simple message pump presented in this 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. The value 1 indicates that you should wait until all the kernel objects in the first parameter are signaled, but my experience suggests that you almost invariably want to stop waiting when any one of these objects is signaled, so this parameter will almost always be 0. 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 True, 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 between win32event.WAIT_OBJECT_0 and win32event.WAIT_OBJECT_0+N-1 (where N is the number of waitable kernel objects in the tuple you passed as the first parameter), inclusive, means that the wait finished because an object was signaled (being signaled means different things for each kind of waitable kernel object). The return code's difference from win32event.WAIT_OBJECT_0 is the index of the relevant object in the tuple.

A return value of 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. (That function, in turn, returns a true result 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.

I suspect that a purist would find some way or other to wrap all of this message pumping into a neat module, letting each application customize its use of the module by passing in a list of waitables, some dictionary to map different waitables to chunks of code to execute, and a partridge in a pear tree. Go ahead, turn it all into a custom metaclass if you wish, see if I care. For once, though, I think the right approach to reusing this code is to copy it into your application's source directories, and use your trusty text editor (gasp!) to tailor the message pump to your application's exact needs.

See Also

Documentation for the Win32 API in PyWin32 (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); Mark Hammond and Andy Robinson, Python Programming on Win32 (O'Reilly).



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net