Win32 Message Loops & Alertable Wait States

By Markus Svilans
msvilans at gmail dot com
13th September, 2006

Home

Revisions

5 September, 2008 -- Fixed typo where the WAIT_TIMEOUT macro was inadvertently redeclared as a const unsigned int. Thanks to Ken Hadden for telling me about this error.

Introduction

Graphical Windows programs are centred around message loops (also known as message pumps). Through various endeavours in Windows programming, I've seen over and over that a good understanding of message loops is a prerequisite to calling oneself a "Windows programmer" (whatever that really means).

In this article I discuss the two main types of message loops that I have had the chance to use. We start with the basic GetMessage() loop, followed by a more advanced loop that uses the MsgWaitForMultipleObjectsEx() API function to enter an alertable wait state while waiting for new messages to arrive. The latter message loop lets you use asynchronous I/O completion callbacks.

Basic Message Loop

The basic Windows message loop is the one you usually write in the WinMain() function of your Windows program. It usually looks a lot like this:

unsigned int MsgLoop()
{
BOOL bRet;
MSG msg;

// GetMessage() will return zero when it receives the WM_QUIT message.
// The message loop will stop at this point.
while ( (bRet = ::GetMessage(&msg, NULL, 0, 0)) != 0 )
{
if (bRet == -1)
{
// TODO: Handle the error and possibly exit.
}
else
{
if (msg.hwnd == NULL)
{
// Thread messages have the hwnd member set to NULL. This
// allows us to process thread messages here. If there is a
// modal dialog open, however, then thread messages will not
// be processed. Other techniques are required in this case.

// TODO: Handle thread messages here.
}
else
{
// Translate virtual key messages into character messages.
::TranslateMessage(&msg);

// Dispatch messages to the window procedures associated
// with the thread in which this message loop is running.
::DispatchMessage(&msg);
}
}
}

return msg.wParam;
}

The GetMessage() function drives the message loop. It waits for new message to arrive, pulls them out of the message queue, and passes them on to your code to process them one by one. Once all messages have been processed, it again waits for more messages to arrive. When the WM_QUIT message is posted to the message queue, GetMessage() returns zero, which causes the while loop to end.

The TranslateMessage() function translates virtual key codes into character messages.

The DispatchMessage() function is more interesting. It looks at the hwnd member of the MSG structure, and calls that window's window procedure to process the message. However, if the message was posted to the message queue using the PostThreadMessage() function, the hwnd member will be NULL. DispatchMessage() simply ignores these messages, so your code will never see them. To process these messages, you have to check if hwnd is NULL and then process the message separately, as in the sample code.

The basic message loop will suffice for probably 99% of all Windows programs that have a graphical user interface. However, if you need to use Windows asynchronous I/O features that use callbacks to signal the I/O operation completion, then it is generally not convenient to use the basic message loop.

Asynchronous I/O

Asynchronous I/O is a powerful feature that lets your program begin a read or write operation, and continue running while the operation is carried out in the background. When the operation completes, you can pick from two mechanisms that notify your program:

  1. An event object signalled, or
  2. An I/O callback procedure invoked.

While the event signalling technique works well enough, I prefer the callback signalling mechanism for all the work I have had to do. It is simpler. Using event objects means you that have to either periodically check to see if the object is signalled, or have a dedicated thread wait for the object to become signalled, using WaitForSingleObject() for example.

To execute the I/O callbacks, your message loop (or more precisely, your thread) must enter alertable wait state. This is done by calling some particular Windows API functions, but because the basic message loop does not call any of these, the thread never enters the alertable wait state, and so no callbacks can run.

The solution is to construct a message loop that enters an alertable wait state while also getting messages, to allow the I/O callbacks to run between messages.

Alertable Message Loop

The alertable message loop is not much crazier than the basic message loop. It actually contains a basic message loop that processes messages that arrive while the thread is waiting in the alertable state. This is the code that I use in many of my programs:

unsigned int MsgLoop()
{
MSG msg;

// Time-out period for MsgWaitForMultipleObjectsEx().
const unsigned long cWaitPeriod = 100;

// Main thread loop.
while (true)
{
// Process any messages in the message queue. This part is similar
// to the basic GetMessage() message queue.
while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
// We have to manually check for the WM_QUIT message, since
// PeekMessage() doesn't automatically handle it like
// GetMessage().

// Return the value that was passed with PostQuitMessage().
return msg.wParam;
}
else if (msg.hwnd == NULL)
{
// Thread messages have the hwnd member set to NULL. This
// allows us to process thread messages here. If there is a
// modal dialog open, however, then thread messages will not
// be processed. Other techniques are required in this case.

// TODO: Handle thread messages here.
}
else
{
// Translate virtual key messages into character messages.
::TranslateMessage(&msg);

// Dispatch messages to the window procedures associated
// with the thread in which this message loop is running.
::DispatchMessage(&msg);
}
}
// End: while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))

// Wait for any message sent or posted to this queue. We are using
// MsgWaitForMultipleObjectsEx so that we enter the alertable wait
// state.
DWORD result = ::MsgWaitForMultipleObjectsEx(0, NULL,
cWaitPeriod, QS_ALLINPUT, MWMO_ALERTABLE);

// TODO: This is a good place to insert your other custom code.
// Any code placed here is guaranteed to execute at least as often
// as the cWaitPeriod value (approximately).

// The result tells us the type of event we have.
if (result == WAIT_FAILED)
{
// TODO: Handle error.
}
else if (result == (WAIT_OBJECT_0 + 0))
{
// In the case of this message loop, we are not waiting on any
// objects, so the return code for new messages is
// WAIT_OBJECT_0 + 0.

// Continue to the top of the while loop to process the messages.
continue;
}
else if (result == WAIT_IO_COMPLETION)
{
// Wait was terminated by an APC being queued to the thread.
}
else if (result == WAIT_TIMEOUT)
{
// Wait timed out. Do nothing in this case. Just continue back
// to the top of the while loop.
}
else
{
// One of the handles became signaled.
// If we were actually waiting on any handles, we would get the
// object index using this expression:
// int index = result - WAIT_OBJECT_0;
}
}
// End main thread loop
}

The main loop is an infinite while loop, that has within it another while loop, which is really just a basic message loop, slightly modified. The key differences are:

  1. The PeekMessage() function is used to get messages from the message queue, in much the same way as GetMessage() is used in the basic message loop.
  2. We have to manually handle the WM_QUIT message, because PeekMessage() handles it just like any other message. In contrast, GetMessage() returns zero when it receives the WM_QUIT message.

As before, we check the hwnd member of the MSG structure to see if it is NULL, so that we can respond to messages posted using PostThreadMessage(). When the PeekMessage() loop runs out of messages, it ends, and the new MsgWaitForMultipleObjectsEx() function is invoked immediately after.

MsgWaitForMultipleObjectsEx() is the interesting part of the loop. When called with the MWNO_ALERTABLE flag, it puts the thread into an alertable wait state, while also waiting for new messages to arrive. It can also wait for synchronization objects to become signalled (such as event objects, mentioned above) but in this case we are not using it for that.

In the above code, the returned value from the function is checked to determine what caused it to return. But, aside from WAIT_FAILED, which indicates an error, we don't really need to do anything with the returned value (unless we were waiting on synchronization objects). Simply calling MsgWaitForMultipleObjectsEx() with the parameters is enough.

Now the message loop has the ability to handle I/O callbacks, while also processing normal messages.

Putting It To Work

The alertable message loop is useful for applications that do a lot of I/O work behind the scenes, yet require a responsive user interface, or need to handle other tasks concurrently.

The ReadFileEx() and WriteFileEx() are purpose-built for asynchronous I/O, in contrast to ReadFile() and WriteFile(), which can be used either synchronously or asynchronously. For example, when ReadFileEx() finishes reading, it queues an I/O callback to your thread, which executes as soon as the thread enters an alertable wait state.

In a future article, I will show some example code of how ReadFileEx() and WriteFileEx() can be used to communicate with external devices (e.g. GPS receivers and magnetometers) via RS-232 ports, using an alertable message loop to smoothly handle the I/O callbacks.

Note

You will notice that in both code examples above, I have packaged the message loop in a MsgLoop() function. This is intentional, and I do the same thing in my programs. There are some important benefits to doing it this way:

  1. The message loop implementation is hidden from the rest of the program.
  2. It is easy to experiment with different types of message loops, while keeping the rest of the program unchanged.
  3. You can call the MsgLoop() function from any thread, to start processing messages within that thread. Combined with a nice C++ thread class, it becomes quite a bit simpler to create multithreaded programs that handle advanced I/O tasks.

Conclusion

The information above is accurate to my knowledge, but if I have made a mistake somewhere then please don't hesitate to correct me. If you have something to add to the above then please also let me know!

-- Markus.

References

MSDN pages on the Windows concepts mentioned:

MSDN reference pages for the Windows API functions used in the sample code: