Credit: Michael Robin
In a Win32 application, you need to process messages, but you also want to wait for kernel-level waitable objects and coordinate several activities.
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")
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.
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).