Bob Gunderson
Microsoft Developer Network Technology Group
Created: December 11, 1992
This article explains the inner workings of GetMessage and PeekMessage and is the foundation for a set of articles that deal with messaging and its effect on tasking in the 16-bit MS-DOS®/Microsoft® Windows™ environment. The following topics are discussed:
There are significant differences between the 16-bit MS-DOS/Windows environment and the 32-bit Win32™/Windows NT™ environment. These differences are not covered here, but are left as topics for future articles.
To understand the operation of GetMessage and PeekMessage, one must first know how the Microsoft® Windows™ operating system stores events and messages. Two types of queues in Windows serve this purpose: the system queue and message queues.
Windows has drivers that are responsible for servicing interrupts from the mouse and keyboard hardware. The keyboard and mouse drivers call, at interrupt time, special entry points in USER.EXE to report the occurrence of an event. The pen driver in Windows for Pen Computing also calls these entry points with raw pen events.
All keyboard, mouse, and pen events are stored in a queue called the system queue, as shown in Figure 1. All running applications share the single system queue. The system queue never contains window messages; its sole duty is to record hardware events as they happen at interrupt time. The only way to get an event into the system queue is to call the special event entry points in USER.EXE.
Figure 1. Flow of hardware events into the system queue
In Windows version 3.1, the system queue is a fixed-length queue with space for 120 entries, which under normal circumstances is plenty of room. If an application hangs or neglects to process messages and events for long periods of time, it is possible for the system queue to fill up. If this happens, any attempt to add new events to the queue will cause the system to beep.
When an application is started, a queue is created on its behalf. This application queue (sometimes referred to as a task queue) is used to store messages that have been posted to one of the application's windows. The only messages that ever reside in an application's queue are those that are explicitly posted using PostMessage or PostAppMessage. (SendMessage never uses the system queue.) The PostQuitMessage function does not post a message to an application queue. (The WM_QUIT message will be discussed later.)
By default, each application queue can hold eight messages. Normally, this is quite adequate because PostMessage is used infrequently. But there are times when an application design forces numerous PostMessage calls to an application. For these applications, the queue size can be increased using the SetMessageQueue function. The SetMessageQueue function must be used with care, however. It deletes the current application queue and creates a new one with the desired size. Any messages in the old queue are destroyed. Because of this, it must be called in your WinMain routine before any other Windows application programming interface (API) or after the application has explicitly emptied the queue with PeekMessage.
Internally to Windows, GetMessage and PeekMessage execute the same code. The major difference between the two is in the case where there are no messages to be returned to the application. In this case, GetMessage puts the application to sleep while PeekMessage returns to the application with a NULL value. There are a few other differences that will be described later, but they are fairly minor.
A step-by-step description of the common GetMessage and PeekMessage code in Windows version 3.1 follows.
Note The order of the steps presented below dictates the priority of a type message. That is, posted messages are always returned before keyboard and mouse messages; keyboard and mouse messages are returned before paint messages; and so on.
Immediately before GetMessage and PeekMessage return a message to the calling application, a test is performed to check for the presence of a WH_GETMESSAGE hook. If one is installed, the hook is called. The hook is not called if PeekMessage finds no messages available and is about to return a NULL value. From the hook procedure, it is impossible to tell whether GetMessage or PeekMessage was called.
As described above, events in the system queue are simply records of hardware events. The primary job of the code scanning the system queue is to create messages from these events and determine which window should receive the message.
The code first retrieves, but does not remove, the event at the head of the system queue. Because mouse and keyboard events are the only two types of events in the queue, the code branches and processes each type of event separately.
The steps for processing mouse events follow.
Figure 2. Window-hit testing for mouse events
If, for example, the arrow in Figure 2 represents the current mouse location, any mouse activity, such as a mouse-button click, will generate an event that will result in a message for Window B.
a) If the value returned is NULL, MA_ACTIVATE, or MA_ACTIVATEANDEAT, the ActivateWindow function is called to activate the ultimate top-level parent.
b) If the value returned is MA_NOACTIVATE or MA_NOACTIVATEANDEAT, the window is not activated.
Note MA_ACTIVATEANDEAT and MA_NOACTIVATEANDEAT cause the mouse-button-down event to be removed from the system queue without a mouse-down message being generated.
c) Finally, a WM_SETCURSOR message is sent to the window to allow the window to properly set the cursor shape.
By default, both PeekMessage and GetMessage remove messages and events from the queues as each message is returned to the application. There are times, however, when an application needs to scan the queues for the existence of messages without removing the message from its queue. For example, an application may be doing some background processing that it would like to terminate as soon as there are messages available to process. The code checking for the existence of messages, however, may not be able to process the message. For this purpose, applications can call PeekMessage with PM_NOREMOVE in the uRemove parameter. If PM_NOREMOVE is used, PeekMessage will return messages as normal, but will not remove them from their queues.
Message-range and window-handle filters allow an application to retrieve messages from the application or system queues that fall within a specified range of messages or that are intended for a particular window. Before a message or event is returned to an application, a test is made to ensure it matches the current message-range and window-handle filters. If a non-null window-handle filter is passed to GetMessage or PeekMessage in the hwnd parameter, only messages destined for that window will be returned to the application. If non-null minimum and maximum message numbers are passed in the uMsgFilterMin and uMsgFilterMax parameters, only messages that are within this range inclusively are returned. The window-handle and message-number filters may be used separately or together.
Applications that do not desire window-handle filtering may pass a NULL value to GetMessage and PeekMessage in the hwnd parameter. Similarly, passing NULL values in both uMsgFilterMin and uMsgFilterMax parameters disables message-range filtering.
It is important to realize that only mouse and keyboard hardware messages, posted messages, WM_PAINT messages, and timer messages can be filtered. Most messages a window procedure receives are sent directly to the window using SendMessage.
If, while scanning the application queue and system queue for messages to return to the application, the code encounters a message or event that could be returned but does not match the message filter, the message or event is left in its respective queue and the scan continues with the next message or event in the queue.
This process of skipping messages with filtering has the potential of causing serious problems if not done correctly. Windows relies heavily on the fact that input is serialized. That is, events occur in a regular and predictable manner. For example, Windows assumes that if a mouse-button-down event is encountered in the system queue, a corresponding mouse-button-up event will be encountered sometime later, further along in the system queue. If an application is filtering messages and removing them, it is possible to break the serialized nature of system-queue events. That is, it would be possible to filter out the mouse-up event, remove it, and have a mouse-down event in the system queue without a corresponding mouse-up event. The same holds true for keyboard events; the system assumes key-down events will have a corresponding key-up event later in the queue. To prevent problems with filtering, follow these general rules:
Normal message-processing loops in an application's WinMain routine usually exit when the GetMessage function returns a FALSE value. Internally, if GetMessage is about to return a WM_QUIT message, it returns FALSE instead of TRUE even though the message returned in the MSG structure (a WM_QUIT message) is valid. The only time GetMessage will ever return a FALSE value is when it is returning a WM_QUIT message. Conversely, PeekMessage returns WM_QUIT messages as it would any other message. Applications that use PeekMessage in their main message-processing loop must explicitly look for the WM_QUIT message.
The PostQuitMessage function does not actually post a WM_QUIT message to the application's task queue. Instead, it sets a flag that is tested inside GetMessage and PeekMessage. WM_QUIT is returned when GetMessage or PeekMessage detects that this flag is set and no other messages or events are pending for the application.
The scheduler inside the Windows kernel is quite simple. Each time a new application (task) is created, a task-descriptor block (TDB) is created and added to the end of the TDB list. (The TDB is sometimes incorrectly referred to as the "task database.") There is one TDB in the list for each task currently running. To the scheduler, every task exists in one of two states: ready to run or asleep. A flag in the TDB determines whether the task is asleep, waiting for some event to occur, or ready to run.
The scheduler only runs tasks that are ready to run. It does this in a round-robin fashion. That is, when a task that has been running yields control, its TDB is moved to the end of the TDB list, and the next task in the list that is not asleep runs. The TDB list is implemented as a circular linked list, so all that really moves is a pointer to the currently running TDB.
The Yield function simply stops running the task (but does not put it to sleep) and runs the next task that is ready to run. The task that called Yield is still ready to run. Although this appears to be the perfect solution for implementing background processing, using the Yield function presents a serious problem, which will be described in "Why You Can't Use Yield" below.
When a task calls GetMessage and there are no messages for the task to process, the task is put to sleep. The task stays asleep until awakened due to activation changes, hardware events, sent messages, posted messages, and so on. For example, if there is nothing in the system queue, and if the task that has the focus window calls GetMessage, the task will most likely go to sleep. The scheduler does not run the task while it is asleep. When Windows receives a keyboard event, the task is awakened and made ready to run. The scheduler will see that the task is ready to run, and then run the task when it is its turn. The task will then check the system queue for events, see the keyboard event, and return the key message from the GetMessage call.
When keyboard or mouse events happen, the system cannot determine which application the event is destined for. For example, suppose a dialog box is active and contains a push button and an edit control with the focus. If the user presses a mouse button and then immediately presses a key on the keyboard, it is possible for the system queue to contain (in this order) a mouse-down event, a mouse-up event, a key-down event, and a key-up event. When the mouse events are processed and the dialog button is clicked, it is possible for the dialog box code to change the focus to a window in a different application. Since keyboard messages can only be retrieved by the application that contains the window with the focus, there is no way for the system to determine which application will receive the key events until after the mouse events have been processed.
In general, at the time of an event, it is impossible for Windows to know which application will be the destination of the message created by the event. But Windows must ensure that some application is awake to process the event. It makes its best guess and wakes the application it thinks might be able to process the event. When a key event happens, Windows ensures that the application that owns the focus window is ready to run (not sleeping). Similarly, for mouse events, Windows ensures that the application that owns the active window is ready to run. This process almost always awakens the proper application, but there are cases where the wrong application is awakened. This is the reason the system-queue-scanning code must check to see if the event about to be processed is destined for the currently running application. If not, it awakens the proper application and yields control to it.
The problem with using the Yield function is not with the function itself, but is a result of input serialization that GetMessage and PeekMessage enforce. Unless an application is filtering messages, events in the system queue are processed in a serial fashion. That is, the event at the head of the queue must be processed before subsequent events in the queue.
The problem can best be illustrated by example. Suppose an application is performing a task that is taking quite a while. No other applications can run until this application yields control. It is very probable that events have been queued in the system queue, especially if the user has moved the mouse or typed on the keyboard. Assume, for this example, that the mouse has been moved in the client area of a window belonging to the application performing the long task. The event at the head of the system queue will then be a mouse-move event destined for this application.
The application now wants to yield control to allow other applications a chance to run. It if tries to do this by calling the Yield function, the following will happen:
The general problem with Yield is that it does not allow the currently executing application to process system-queue events. It should not be used by applications to yield control in situations where system-queue events may occur for the application.
The WaitMessage function is one of the simplest Windows functions. It simply puts the current task to sleep. It is normally used by applications that perform background processing to control precisely when their application goes to sleep. Most applications that do background processing implement a PeekMessage loop in the following manner (this is pseudocode):
if (PeekMessage(...) != NULL)
// Translate & dispatch the message
else if (there is background processing to perform)
// Perform background processing
else
// No background processing, no messages - go to sleep
WaitMessage();
Used in this manner, the application puts itself to sleep using WaitMessage if there is no background processing to perform and there are no messages to process. It is important that applications do not sit and spin in a PeekMessage loop when in an idle state. An application spinning in a PeekMessage loop will not appear to Windows as idle. This will prevent Windows from performing idle processing like flushing disk cache buffers and performing power management optimization on battery-powered systems.