Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Inside the Windows Messaging System


FEB93: INSIDE THE WINDOWS MESSAGING SYSTEM

Matt, who works for a California programming-tools vendor, specializes in debuggers and file-format programming. This article contains material that will appear in greater detail in Matt's upcoming book, Windows Internals (Addison-Wesley, 1993). He can be contacted through the DDJ offices.


The Windows messaging system is like a heart: It pumps the lifegiving message stream on which all Windows apps depend. Windows messages signal when the mouse moves, a menu item is selected, and a window is created. Dialogs, menus, and other controls rely on messages to communicate with each other; messages also serve as a form of interprocess communication. Even the KERNEL module, which is supposed to lie below the level of the messaging system (implemented in USER.EXE), uses messages to indicate changes in the global heap. Truly understanding Windows means becoming familiar with the inner workings of its messaging system.

This article provides a detailed look at this complex, not fully documented area of Windows 3.1 and presents pseudocode for key routines such as GetMessage(), DispatchMessage(), PeekMessage(), and SendMessage(). I also cover internal functions in Windows that even Undocumented Windows (Addison-Wesley, 1992) does not discuss; these are presented using their real names, which I obtained by examining the symbolic information in the debugging versions of the Windows DLLs.

The Five Kinds of Messages

There are five ways that messages enter the message stream. I used GetQueueStatus(), newly documented and improved in Windows 3.1, to look at return values (QS_*), which are defined in WINDOWS.H. The five categories are:

  • Input messages (values of QS_KEY, QS_MOUSEMOVE, and QS_MOUSEBUTTON). Although GetQueueStatus() assigns different QS values, you can consider them all to be input messages generated by hardware devices, which get stored in the shared system message queue.
  • Posted messages (QS_POSTMESSAGE). These messages are placed in the application message queue via PostMessage() or PostAppMessage(). There's one application message queue per program.
  • Paint messages (QS_PAINT). Like QS_TIMER messages, paint messages don't wait in a queue, but are generated as needed when an application requests a message. The Windows window manager is responsible for knowing if a particular window needs updating. When a window region is invalidated, the messaging system is informed that a repaint is necessary (the QS_PAINT flag is set). Then, when an application asks for a message, a WM_PAINT message is composed.
  • Timer messages (QS_TIMER). These are similar to QS_PAINT; both are generated on-the-fly when an application calls GetMessage() or PeekMessage(), instead of being stored in message queues, and thus they do not fill up the queues.
  • Sent messages (QS_SENDMESSAGE). SendMessage() sends a message to any window and guarantees that the receiving window will reply before anything else occurs. Sending messages between two windows of the same application is not hard; sending messages between two different tasks is more difficult. Because each window procedure must operate in its normal task context, the Windows scheduler must come into play. Accomplishing this correctly involves synchronization between the two tasks.
These distinctions are not based on the message number (such as 0x000F), but on how the message came into existence. For instance, the WM_PAINT message is normally synthesized when your application calls GetMessage(). Your program doesn't have to care how the WM_PAINT message was created. On the other hand, it's perfectly legal for an application to use SendMessage() to send a WM_PAINT message to another window. This message will be seen in the queue as QS_SENDMESSAGE rather than as a QS_PAINT message. Likewise, you can do a PostMessage() of a WM_PAINT message, which results in a QS_POSTMESSAGE-type message. The message numbers aren't important for this discussion; it is important that there are multiple ways to introduce messages into the system. (Incidentally, you wouldn't want to send or post WM_PAINT messages; I'm only using this example because this message can be generated three different ways.

The Application Message Queue

Every window in the system is associated with a particular application message queue. In reviewing the fields in a WND data structure (described in Undocumented Windows), note the one that contains a message-queue handle. When a message is posted, this field determines to which queue the message will be added. Even the desktop window has a message queue associated with it.

But the application queue is much more than a holding area for posted messages. Because it contains most of the data used by the Windows messaging system, think of the queue as a sort of command center linking a window handle to a particular task, and serving as the keeper of the status bits vital to GetMessage()/PeekMessage(). The application message queue is closely tied to the application's task database (TDB). Message-queue fields contain the selector of the associated TDB, and vice versa.

At startup, a program's message queue is created by the InitApp() routine. Memory for the application message queue comes from the global heap. You can obtain a handle to the current message queue via the undocumented GetTaskQueue() (USER.35), whose prototype is HANDLE FAR PASCAL GetTaskQueue(HANDLE hTask). If you pass it an hTask value of 0, you'll get the current task's queue.

Messages are placed in the application's queue via PostMessage(). Some internal Windows functions will also call PostMessage() behind the scenes--DefWindowProc() for instance.

The default size for an application message queue is eight messages, usually enough to contain all the messages actually posted to an application. Typically, more messages are sent directly to the window via SendMessage(). You can alter the size of the application queue with SetMessageQueue(). Call this function before any windows are created, because the old message queue gets deleted and a new one created, and this causes confusion if the original message queue is already in use. An alternative to using SetMessageQueue() is to modify the DefaultQueueSize setting in WIN.INI. This is an undocumented key, so you may have to add it if it's not present.

The Windows 3.1 application message-queue structure is in Listing One (page 100). The queue contains data for several purposes. One is to maintain a circular queue of messages. This queue, similar in concept to the ROM-BIOS keyboard buffer, contains read and write pointers which wrap back to the beginning when past the end of the buffer and indicate where the next message will be read from and written to.

The application message queue also supports SendMessage() between tasks by storing the parameters, return values, and current state of the transaction. The section used is not the one for posted messages because sent messages are guaranteed to be processed immediately, ahead of other waiting messages.

To illustrate how to access the contents of the message queue, I wrote a program, Queue.C, which is available electronically; see "Availability," page 5.

The System Message Queue

The system message queue is a kind of half-brother to the application message queue. The system message queue's job is to hold all hardware-input messages. This includes mouse, keyboard, and other input-device events.

In general, hardware events occur at a good clip. Moving your mouse across the screen causes dozens of WM_MOUSEMOVE messages. In order not to lose any of these messages, the system queue's capacity is larger than that of the application queue, containing by default 120 messages. (You can change this by modifying or adding the Type-Ahead entry in WIN.INI.)

The system queue is also allocated and initialized by USER.EXE. There's only one system queue for Windows. The format of the system queue is the same as the application queue, except for stored messages. But the only fields of the system queue actually used are those that implement the circular message buffer.

There's no API to obtain the handle of the system queue, but you can get its handle via a sneaky hack. The first WORD in the segment 0x2C of USER contains the system queue's handle. (In Windows 3.0, it's the WORD at offset 2 of segment 0x2B.) The GlobalEntryModule() function in ToolHelp provides a way to obtain a segment's selector handle, given its ordinal number in the module.

More Details.

Messages in the system queue are not destined for a particular window because the processing of one system message can affect which window/task subsequent messages go to. For instance, a WM_LBUTTONDOWN message can cause a change of focus. Subsequent messages in the queue must then go to the new focus window rather than the previous one.

On the other hand, the system queue can be locked by a task, ensuring that no other task reads system queue messages until the locking task is done. For example, a double-click message is synthesized out of a series of button up/down messages. One task shouldn't steal messages in the middle of the process. The system queue is unlocked when no messages are left for a task, or when another task's message is found.

How do events get into the system queue? In USER.EXE, EnableInput() calls the mouse and keyboard drivers enable functions (ordinal entry #2). Their parameters are the addresses of the exported USER functions mouse_event() and keybd_event(), respectively; mouse_event() and keybd_event() are essentially interrupt-level functions. When the mouse is moved or a key is struck, a hardware interrupt is generated. The DOS-extender subsystem in Windows vectors control to the appropriate interrupt-handler function in the mouse or keyboard device driver (typically called MOUSE.DRV and KEYBOARD.DRV). The mouse and keyboard drivers then call mouse_event() and keybd_event() via the function pointers passed during the enablement process. Processing occurs inside mouse_event() and keybd_event() to place appropriate values in registers before calling SaveEvent().

SaveEvent() places the message in the system queue via a call to WriteSysMsg(), then attempts to coalesce multiple WM_KEYDOWN messages that result from autorepeating keys. Lastly, it calls WakeSomeone(), which determines the best application candidate to receive the message. When an application is found, flags are set in that app's message queue, and an event is posted to its TDB. The application wakes up and receives the message. Pseudocode for WakeSomeone() is in Listing Two, page 100.

In Listing Two, the test for hQCapture implements the Windows capture mechanism. When your application calls SetCapture(), hQCapture is set to the queue associated with the hwnd parameter to SetCapture(). If hQCapture is nonnull inside WakeSomeone(), the hQCapture queue receives the QS_MOUSE event instead of the queue which would ordinarily have received it. If Windows is in a system modal state, the hQSysModal queue is highest in the pecking order, ahead of the hQCapture queue.

WakeBits, WaitEvent, and the Scheduler

If no messages are waiting for processing inside GetMessage(), the system allows other programs to retrieve pending messages. Before describing how this happens, I'll define a few terms:

WakeBits. Bitfields located at offset 44 in the message queue that indicate that a particular kind of message (QS_PAINT, QS_TIMER, and so on) is available to the task. For instance, QS_PAINT means a paint message is waiting for the application, but hasn't been retrieved. Only QS_POSTEVENT messages exist in the application message queue; other message types imply messages synthesized by the system.

More Details.

WakeMask. This value, at offset 46 in the message queue, is a mask of the QS_xxx message types that the application is actively waiting for. Typically, GetMessage() is called with wMsgFilterMin and wMsgFilterMax set to 0. This sets the WakeMask to include all the QS_xxx message types. If you specify an actual range of messages in the GetMessage() call, then an appropriate set of QS_xxx bits will be generated inside of GetMessage().

ChangeBits. This field, at offset 42 in the message queue, contains QS_xxx bits that have changed since the last call to GetQueueStatus(), GetMessage(), or PeekMessage().

Now look at Listing Three, page 100. GetMessage() calls SleepHq() to wait for a message, but still yields to other tasks if they have messages. The messaging system checks for sent messages in many places because these messages must be processed immediately. SleepHq() really wants to wait for a QS_POSTMESSAGE, or a QS_PAINT, or whatever; but if it sees a pending QS_SENDMESSAGE flag, it calls ReceiveMessage() to deal with it immediately, and then goes back to its normal business.

Because SendMessage() processing is dealt with inside SleepHq(), your application does not have to do anything special to receive sent messages--it comes free when you call GetMessage(). Your application cannot receive sent messages at any arbitrary time, only inside of GetMessage()/PeekMessage(), when you call SendMessage(), or when calling a function that uses SendMessage() (such as a dialog-box function). So if your program is crunching a long series of numbers, there's no worry that a sent message will unexpectedly arrive and disrupt processing.

The event-count field, located at offset 6 of the TDB, is like a flag on a mailbox. If it's up (contains a nonzero value), then there's a reason to switch to the task because something is waiting for it, as signified by the WakeBits in the message queue (see Listing Four, page 100). The scheduler doesn't know why the task should be awakened, just that it's necessary. WaitEvent() thus waits for the mailbox flag to pop up. SleepHq() is responsible for checking the mailbox, and either waiting some more for a desired QS_xxx letter, or returning when it finds what it wants. If it sees a QS_SENDMESSAGE in the mailbox, SleepHq() takes it out, deals with it promptly, and goes back to waiting for the desired QS_xxx letter. (For more information on the event-count field, see my article, "Inside the Windows Scheduler," DDJ, August 1992.)

Where do the QS_xxx bits come from? SetWakeBit2() is responsible for setting the WakeBits in the application's message queue, as well as ensuring that the program will be scheduled so that it can respond to the message. Pseudocode for SetWakeBit2() is in Listing Four. SetWakeBit2() is heavily used, and called by these USER routines:

WakeSomeone() sets the QS_MOUSE or QS_KEY bits; it's called by the hardware-event handlers when a message has been added to the system queue.

IncPaintCount() sets the QS_PAINT bit; it's called when a window region is invalidated.

SendMessage() sets the QS_SENDMESSAGE bits in the queue of the receiving task during an intertask SendMessage() so that the task will wake up and process the message.

ReceiveMessage() sets a bit not included in the previously defined QS_xxx bits when the receiving task is done processing the message during an intertask SendMessage() and needs to wake up the sending task to receive the result.

ScanTimers() sets the QS_TIMER bit if sufficient time has elapsed; it's called by the timer interrupt service routine.

WriteMessage() sets the QS_POSTMESSAGE bit. PostMessage() and PostAppMessage() call PostMessage2(), which uses WriteMessage() to put the message in the application's queue.

Bringing it All Together

GetMessage() and PeekMessage() are really front ends for a call to GetMessage2(), which does most of the actual work. The pseudocode for the GetMessage()/PeekMessage() front ends and for the workhorse GetMessage2() is in Listing Five, page 100. Listing Six (page 102) presents pseudocode for CheckForNewInput().

Here's how each of the five types of messages are dealt with in GetMessage()/PeekMessage():

QS_SENDMESSAGE. CheckForNewInput() is called several times in GetMessage2(). Its priority is checking for sent messages. If GetMessage2() ends up sleeping, via SleepHq(), sent messages are checked for in SleepHq() code.

QS_POSTMESSAGE. ReadMessage() extracts the message from the application message queue. The message fields are copied into the addresses specified in the GetMessage()/PeekMessage() call.

QS_MOUSE and QS_KEY. ScanSysQueue() extracts the message from the application message queue. The message fields are copied into the addresses specified in the GetMessage()/PeekMessage() call.

QS_PAINT. DoPaint() extracts the message from the system message queue. The message fields are copied into the addresses specified in the GetMessage()/PeekMessage() call.

QS_TIMER. DoTimer() writes the timer message into the application queue. GetMessage2() then starts at the beginning, and finds the timer message as if it were a normal PostMessage().

A couple of conclusions can be drawn from the code. First, GetMessage()/PeekMessage() will not yield to other applications if messages are waiting. Second, there's a definite pecking order of message priorities. Messages sent via SendMessage() always have top priority. This is necessary because the task that did the SendMessage() is cooling its heels, waiting for the reply. Next in priority are messages posted via PostMessage(). Messages from the input system (mouse and keyboard) come after that, and then WM_PAINT messages. WM_PAINT messages are handled after other messages because processing of other messages might generate additional paint operations. Processed at the very end, just before GetMessage2() gives up, goes to sleep, and yields to other tasks, are WM_TIMER messages.

How DispatchMessage Works

Once your application has retrieved a message, you're expected to deal with it--typically, by dispatching it to the appropriate window. Rather than requiring you to determine the address of the window procedure and call it directly, Windows provides DispatchMessage(); see Listing Seven, page 102.

DispatchMessage() is straightforward, except for a few things. At the start of the code, there's special handling for WM_TIMER and WM_SYSTIMER messages. If the lParam field of the message is nonzero, a user-supplied callback is called instead of the standard window procedure. The SDK documentation for SetTimer() describes how to use timers.

Also, DispatchMessage() handles "bad" programs that don't call BeginPaint() in their WM_PAINT handler. Apparently, Microsoft feels that it's enough of a problem that DispatchMessage() always checks if BeginPaint() was called by the app's message handler. If the program didn't call BeginPaint(), Dispatch Message() goes ahead and does some default painting to correct the situation (and whine at you with a debug message if you're running the debug version of Windows).

Lastly, you might notice that, before your program's window procedure is called, DS is set to the hInstance of the application. This compensates for applications that fail to export their callback functions. Under Windows 3.0, this may result in a GP fault (due to an invalid DS) when your window procedure gets called. With Windows 3.1, some people claim you no longer have to export functions or call MakeProcInstance(). This may or may not be sound advice, but Microsoft seems to feel that setting DS is a worthwhile activity for DispatchMessage().

Anatomy of a SendMessage Call

SendMessage() is one of the most frequently used Windows functions, yet perhaps the least understood. Many programmers mistakenly assume that SendMessage() just calls the appropriate window procedure. They forget that Sendmessage() needs to operate in two different task contexts when one application sends a message to another.

This situation can become rather complex. The receiver of a "sent message" might need to send a message to another task before it can respond to the original message, resulting in nested calls to SendMessage(). The processing of an intertask SendMessage() is shown in Listing Eight (page 102). Listing Nine presents pseudocode for ReceiveMessage(), and Listing Ten (page 103) is ReplyMessage().

As you can see from the pseudocode, handling the case where an application sends a message to itself is straightforward. The parameters are pushed on the stack, and the window procedure is called. The bulk of the code in SendMessage() is for handling situations in which the receiving window is in a different task. Within the intertask SendMessage() code and in ReceiveMessage() and ReplyMessage(), a large amount of code has to do with handling nested SendMessage() calls. As these calls pile up on top of each other, the system builds a linked list which specifies the message queues waiting for SendMessage() to return. The most recent queue is at the head of the list. As each message is replied to, the head of the list is removed, and the list shrinks.

Although not normally done, your application program can call ReplyMessage() (within a WH_CALLWND-PROC hook, for example) to prevent the window which ordinarily would get the message from actually receiving it. It's also useful to call ReplyMessage() when handling a message sent to you via SendMessage(). The sending program cannot execute until your program finishes processing the message. When handling the message, if your program calls a Windows function that yields control, such as MessageBox(), a potential deadlock situation can arise. A call to ReplyMessage() before this will avoid the deadlock.

--M.P.

Why's it So Hard to Write a GUI Debugger?

The fatal flaw in the Windows input system is that it is "single threaded." If your application fails to call GetMessage() or PeekMessage() in a timely manner, the system locks up. You can still move the mouse, and background processing in Enhanced-mode DOS boxes continues, but none of the Windows applications can respond to mouse or keyboard input because they aren't given a chance to run.

Say your database program gets a WM_COMMAND message, which it interprets to mean, "Go sort this database of 300,000 records," and dutifully conducts this 45-minute operation; during that time, all apps are locked out until your next call to GetMessage(). The polite thing is for your program to call PeekMessage() occasionally, thus yielding to other applications.

A quirk in the messaging system rears its head when you try to write a Windows-hosted debugger (also called a "GUI debugger"). A GUI debugger is a debugger for Windows programs that itself uses the Windows display mechanisms. What's the problem with that? Well, imagine the following scenario: A GUI debugger places a breakpoint inside of a Window procedure. Eventually, the debuggee program hits the breakpoint, and stops--and cannot call GetMessage() to yield control to other tasks! That means no other tasks--including the GUI debugger--can get their messages. The debugger can't even respond the mouse clicks that tell the debuggee to run again.

You may ask: But there are GUI debuggers available, so how do they deal with this?

Unfortunately, the answer is, "not extremely well." When the debuggee hits the breakpoint (or stops for any reason), the GUI debugger must take over the duties of calling GetMessage() and DispatchMessage() for the debuggee. The debugger must prevent any code in the debuggee process from running. To do this, the debugger needs to somehow intercept all messages that would normally go to the debuggee, and deal with them instead.

One way to accomplish this is by subclassing all of the debuggee's windows. The question then arises: How do you deal with all the messages originally intended for the debuggee? The debugger surely doesn't know how to paint the debuggees windows in response to a WM_PAINT message. Situations where message ordering is critical, such as DDE transactions, are even harder to deal with. Unfortunately, there's no perfect solution. GUI debugger designers deal with this as best they can. This explains why both Borland's Turbo Debugger for Windows and Microsoft's Codeview for Windows are text-mode debuggers. In Win32, the input mechanism has been redesigned (although by the same person who designed the Windows and OS/2 PM input systems). A major goal was to eliminate the input-system problem described above. Consequently, Win32 uses a separate input queues for each task. A thread in the Win32 subsystem continually assigns messages to the appropriate applications queue as input events occur. This lets programs deal with messages in their own sweet time, without adversely affecting the responsiveness of the system as a whole. Unfortunately, this improved functionality does not extend to Win32s applications. Under Win32s, the Windows 3.1 USER.EXE module is still in charge of the input system, thereby causing Win32s applications to be in the same boat as regular Windows programs.

--M.P.



_INSIDE THE WINDOWS MESSAGING SYSTEM_
by Matt Pietrek


[LISTING ONE]
<a name="0091_000f">

00h WORD    Selector of next message queue, (implements linked list).
02h WORD    hTask of task that owns this queue.
04h WORD    Size of a message in this queue.  (In Windows 3.1, this is 22).
06h WORD    Number of messages waiting that have not been removed
            by a GetMessage() or PeekMessage(PM_REMOVE).
08h WORD    Offset in the queue segment of next message to be retrieved.
0Ah WORD    Offset in the queue segment where next message will be written.
0Ch WORD    The length in bytes of the queue's segment.
0Eh DWORD   DWORD value returned by GetMessageTime().
12h DWORD   DWORD value returned by GetMessagePos().
16h WORD    Unknown.  Sometimes contains 1.
18h DWORD   Information returned by GetMessageExtraInfo().
1Ch WORD    Unknown.
1Eh DWORD   Contains the LPARAM of a SendMessage() to another task.
22h WORD    Contains the WPARAM of a SendMessage() to another task.
24h WORD    Contains the MSG of a SendMessage() to another task.
26h WORD    Contains the HWND of a SendMessage() to another task.
28h WORD    Contains the DWORD result from the SendMessage().
2Ch WORD    PostQuitMessage() has been called by this program.
2Eh WORD    PostQuitMessage() exit code.
30h WORD    Flags of some sort.
32h DWORD   Unknown.
36h WORD    Expected Windows version, from NE file.
38h WORD    Queue handle of application that is sending a message to this app.
3Ah WORD    Used for an intertask SendMessage().
3Ch WORD    Used for an intertask SendMessage().
3Eh WORD    Number of "paints" needed by this application.
40h WORD    Number of timer events waiting for this application
42h WORD    QS_xxx bits that have changed since the last call to
            GetMessage(), PeekMessage(), or GetQueueStatus().
44h WORD    QS_xxx bits indicating the kind of messages that are waiting
            for the application.
46h WORD    Contains the QS_xxx bits that an application is
            currently waiting for.
48h WORD    Used for intertask SendMessages().

4Ah WORD    Used for intertask SendMessages().
4Ch WORD    Used for intertask SendMessages().
4Eh WORD    Something having to do with hooks
50h BYTE[1Eh]  Unknown.  Possibly having to do with hooks.
6Eh WORD    Start of the posted message storage area.  The
            memory from here, to the end of the segment, can
            be thought of as an array of messages, each message
            being 22 bytes in length.






<a name="0091_0010">
<a name="0091_0011">
[LISTING TWO]
<a name="0091_0011">

// Global variables: hQCursor   - The queue "associated" with the cursor
//          hQActive   - The queue of the "active" window that has focus
//          hQCapture  - The queue associated with the capture window
//          hQSysModal - The queue associated with the system modal window
// Local variables: best_queue - contains the current "best guess" as to which
//                       queue should be woken up to receive the message
//          wakebit - contains the QS_xxx message type (QS_MOUSEMOVE,
//               QS_MOUSEBUTTON, or QS_KEY) that will be placed in the WakeBits
//                    of whatever queue is selected to receive the message.
    best_queue = hQCursor
    if ( message is a not a key message )
        goto mouse_event
    wakebit = QS_KEY
    if ( hQActive != NULL )
        best_queue = hQActive
    goto system_modal_check
mouse_event:
    if ( message == WM_MOUSEMOVE )
        wakebit = QS_MOUSEMOVE
    else
        wakebit = QS_MOUSEBUTTON
    if ( hQCapture != NULL )
        best_queue = hQCapture
system_modal_check:
    if ( hQSysModal != NULL )
        best_queue = hQSysModal
    if ( best_queue != 0 )
        goto wake_em_up
    iterate through queue linked list
    {
        if ( queues WakeMask includes wakebit determined
            previously )
        {
            best_queue = current queue under examination

            goto wake_em_up
        }
        if ( at end of queues linked list )
            return
    }
wake_em_up:
    SetWakeBit2();      // Sets WakeBits, and posts event
    return





<a name="0091_0012">
<a name="0091_0013">
[LISTING THREE]
<a name="0091_0013">

// WakeMask contains QS_xxx OR'ed together. SleepHq() will not return until at
// 1 of QS_xxx bits in the WakeMask parameter has been set in the ChangeBits.

void SleepHq( unsigned WakeMask )
{
    HANDLE currQ
SleepHq_check_flags:
    currQ = Get_current_task_queue
    // If already have a message then go get it
    if ( WakeMask & currQ.ChangeBits )
        goto SleepHq_done
    // Check for SendMessages and deal with them
    if ( currQ.WakeBits & QS_SENDMESSAGE )
        goto SleepHq_have_SendMessage
    // Always check for SendMessages
    currQ.WakeMask = WakeMask & QS_SENDMESSAGE
    if ( WakeMask & currQ.ChangeBits )
        goto SleepHq_done
    WaitEvent()     // Kernel routine that waits for an event
    goto SleepHq_check_flags:
SleepHq_done:
    zero_out_currQ.WakeMask
    return
SleepHq_have_SendMessage:
    zero_out_qWakeMask
    // Deal with the SendMessage(). Described in the section on SendMessage()
    ReceiveMessage()
    goto SleepHq_check_flags

}





<a name="0091_0014">
<a name="0091_0015">
[LISTING FOUR]
<a name="0091_0015">

void SetWakeBit2(HANDLE hQueue, UINT WakeBit)
{
    hQueue.ChangeBit |= WakeBit     // Turn on the QS_xxx flags
    hQueue.WakeBit   |= WakeBit
    // If we're setting a QS_xxx bit that the queue is waiting
    // for, then force the scheduler to schedule the task
    if ( WakeBit & hQueue.WakeMask )
    {
        hQueue.WakeMask = 0
        PostEvent() to hQueue's task
    }
}






<a name="0091_0016">
<a name="0091_0017">
[LISTING FIVE]
<a name="0091_0017">

// "flags" are the "flags" parameter to PeekMessage(). "removeFlag" is a local
//  indicating whether a message will be read from the queue. "WakeMask" is a
//  local containing a QX_xxx mask of messages types GetMessage()/PeekMessage()
//  are waiting for. "WakeBits" is a local containing the the QS_xxx bits that
// indicate which types of messages are waiting for this task.

PeekMessage:
    Is_GetMessage_call = 0
    goto GetMessage2
GetMessage:
    Is_GetMessage_call = 1
    Insert a flags WORD in the stack frame so that the stack
    frame for GetMessage() is the same as for PeekMessage().
    The flag is set to PM_REMOVE.
GetMessage2:    // This is where GetMessage() and PeekMessage()
                // start sharing their code
    if ( current task is locked )
        set PM_NOYIELD in flags
    removeFlag = flags & PM_REMOVE
    Unlock the system queue if this task holds it.
    if ( (msgMin != 0) or (msgMax != 0) )
        Call function to set up WakeMask for the specified
        message range
    else
        WakeMask = QS_MOUSE | QS_KEY | QS_POSTMESSAGE
                    | QS_TIMER | QS_PAINT

begin_looking_for_msgs:
    if ( !CheckForNewInput() )
        goto wait_for_input
    if ( system queue not locked )
        goto not_in_system_queue
    if ( system queue not locked by current queue )
        goto not_in_system_queue
    if ( (QS_MOUSE | QS_KEY) set in WakeMask and WakeMask )
    {
            if ( ScanSysQueue() )
                goto GetMessage_have_msg
    }
not_in_system_queue:
    if ( QS_POSTMESSAGE set in WakeBits and WakeMask )
        if ( ReadMessage() )
            goto GetMessage_have_msg
    if ( (QS_MOUSE or QS_KEY) set in WakeBits and WakeMask )
        if ( ScanSysQueue() )
            goto GetMessage_have_msg
    if ( !CheckForNewInput() )
        goto wait_for_input
    if ( QS_PAINT set in WakeBits and WakeMask )
        if ( DoPaint() )
            goto GetMessage_have_msg
    if ( PM_NOYIELD set in flags )
        goto check_for_timer_msg
    UserYield()
    if ( !CheckForNewInput() )
        goto wait_for_input
check_for_timer_msg:
    if ( QS_TIMER set in WakeBits and WakeMask )
        if ( DoTimer() )
            begin_looking_for_msgs
wait_for_input:
    if ( FSHRINKGDI )
        ShrinkGDIheap()     ; Where is this defined???
    // If not in GetMessage, we must be in PeekMessage
    if ( Is_GetMessage_call == 0 )
        goto PeekMessage_exit
    SleepHq(wakemask)
    goto begin_looking_for_msgs

GetMessage_have_message:
    if ( a WH_GETMESSAGE hook is installed )
        call the hook function
    // If not in GetMessage, we must be in PeekMessage
    if ( Is_GetMessage_call )
        return 1
    if ( returning msg == WM_QUIT )
        return 0
    else
        return 1
PeekMessage_exit:
    if ( ! PM_NOYIELD )
        UserYield()         // Yield to any higher priority app
    return 0





<a name="0091_0018">
<a name="0091_0019">
[LISTING SIX]
<a name="0091_0019">

// Returns Zero Flag set if no desired input flag is set. WakeMask & WakeBits
// are in registers, and are same as WakeMask and WakeBits in GetMessage2().
top:
    Get handle of current queue
    if ( QS_SENDMESSAGE set in the queues wakebits )
    {
        ReceiveMessage()
        goto top
    }
    // AND instruction sets the Zero flag if any bits match
    AND WakeMask, WakeBits together
    Return





<a name="0091_001a">
<a name="0091_001b">
[LISTING SEVEN]
<a name="0091_001b">

    LPMSG lpMsg    // ptr to passed-in message, used as scratch variable.
    if ( (msg != WM_TIMER) && (msg != WM_SYSTIMER) )
        goto handle_normally
    if ( msg.lParam == 0 )
        goto handle_normally
    GetTickCount()
    push msg parameters on stack
    lpMsg = msg.lParam  // Timer function callback address
    AX = SS     // Something with MakeProcInstance thunk???
    goto call_function
handle_normally:
    if ( msg.hwnd == 0 )
        return;
    push msg parameters on stack
    if ( msg.msg == WM_PAINT )
        set "paint" flag in WND structure
    lpMsg = Window proc address // stored in WND data structure;
                                // pointed to by msg.hwnd
    AX = hInstance from WND structure   // For use by MakeProcInstance() thunks
call_function:
    ES = DS = SS    // Set all segment registers to hInstance of application
    call [lpMsg]    // Call the window proceedure (or timer callback fn).
                    // lpMsg is now used to store the address of window
                    // function (or timer callback function) to be called
    if ( msg.msg != WM_PAINT )
        goto DispatchMessage_done
    // Check for destroyed window
    if ( ! IsWindow(msg.msg) )
        goto DispatchMessage_done
    if ( "paint" flag in wnd structure still set )
        goto No_BeginPaint
DispatchMessage_done:
    return
No_BeginPaint:
    Display debugging message "Missing BeginPaint..."
    Call DoSyncPaint() to handle the painting correctly
    goto DispatchMessage_done





<a name="0091_001c">
<a name="0091_001d">
[LISTING EIGHT]
<a name="0091_001d">

    if ( receiving HWnd == -1 )
        goto BroadcastMessage   // Not included here
    Verify sending app has a message queue
    Get receiving apps queue from receiving hWnd
   // Are the sending and receiving queues the same???
    Intertask = ( receivingHQueue == sendingHQueue )

    Call any installed WH_CALLWNDPROC hooks
    if ( Intertask )
        goto InterTaskSend
   // Next section deals with calling a window proceedure within same program
   // This is the simple case and is much easier than calling between two
   // different programs (below)
    Push address of the wndproc of the receiving WND structure on stack
    Push SendMessage params on stack
    Put hInstance into AX
    Load DS & ES from the SS register
    Call through the wndproc address in the window structure
SendMessage_done:
    Return to caller
SendMessage_error:  // Common JMP location when errors occurr
    Put 0 in DX:AX
    Goto SendMessage_done
    // SendMessage()'s that go between different tasks come here.
    // This is where the code gets complex.
InterTaskSend:
    if ( A task is locked )
    {
        display a diagnostic in debugging version
        Goto SendMessage_Error
    }
    if ( sending task is terminating )
    {
        display a diagnostic in debugging version
        Goto SendMessage_Error
    }
    if (SendMessage parameter area in sending app is already used)
    {
        display a diagnostic in debugging version
        Sleep until the parameter area is free  // Uses SleepHq()
    }
    Grab parameter area in sending app
    Save the address where the result of the call will be stored
    Copy the SendMessage parameters off the stack into the sending hQueue
    Put the receiving queue at the head of the SendMessage() list
    // Set bits to wake up the receiving task

    SetWakeBit2( QS_SENDMESSAGE )
SendMessage_wakeup_receiving_task:
    if ( a previous SendMessage() has completed )
        goto got_reply
    Turn off "have result" flags in sending queue
    Call DirectedYield() to force the child task to run next
    // When the DirectedYield() returns, the receiving task should have awoken
    // and called ReceiveMessage() and ReplyMessage(). Described below.
    Sleep until result is back from child
    // Uses SleepHq(). Probably redundant, because there already should be a
    // result available when the prior DirectedYield() returned.
got_reply:
    Copy the return value to the "result" area on the stack
    Release parameter area in sending queue
    if ( Not replied to )
        goto SendMessage_wakeup_receiving_task
    goto SendMessage_done





<a name="0091_001e">
<a name="0091_001f">
[LISTING NINE]
<a name="0091_001f">

    Make sure there is a SendMessage waiting for us.
    Remove sending queue from SendMessage() list of queues.
    Clear QS_SENDMSG bit if the list of queues is empty.
    Save copies of the sending hQueue and pointer to area
          where results should be saved in the sending task.
    Free the the SMPARAMS area in the sending queue.
    Make sure target window is still valid.
    Copy the ExtraInfo data from sender to receiver.
    Call the target window proc.
    Call ReplyMessage.
    Return.




<a name="0091_0020">
<a name="0091_0021">
[LISTING TEN]
<a name="0091_0021">

    // Reply message takes the value that should be returned to
    // the sender as a parameter.  Here, it's called "return_value"
ReplyMessage_start:
    If ( message has already been replied to, or
         if there is no sending queue )
        return
    if ( QS_SENDMESSAGE bit set in receiving queue)
    {
        ReceiveMessage()
        Goto ReplyMessage_start

    }
    if ( result area in use )
    {
        OldYield()
        Goto ReplyMessage_start
    }
    Copy return_value into sending hQueue
    Restore pointer to result area on stack in the sending hQueue
    Set AlreadyRepliedFlag
    SetWakeBit2( QS_SMRESULT )
    DirectedYield(SendingTask)
    Return








<a name="0091_0022">

//=================================
// LISTING 1: QUEUE.C
//
//  QUEUE, by Matt Pietrek, 1992
//
//=================================

#include <windows.h>
#include <dos.h>
#include "winio.h"

// If your IMPORT.LIB or LIBW.LIB doesn't include
// GetTaskQueue(), you'll have to add it to the IMPORTS section
// of the .DEF file.  The ordinal number is KERNEL.35

WORD FAR PASCAL GetTaskQueue(WORD hTask);

typedef struct
{
   DWORD   extraInfo;
   HWND   hwnd;
   WORD   message;
   WORD   wParam;
   DWORD   lParam;
   DWORD   time;
   POINT   pt;
} QUEUEMSG;

typedef struct
{
    WORD      NextQueue;
    WORD      OwningTask;
    WORD      MessageSize;
    WORD        NumMessages;
    WORD        ReadPtr;
    WORD        WritePtr;
    WORD        Size;
    LONG        MessageTime;
    POINT       MessagePoint;
    WORD      Unknown1;
    DWORD      ExtraInfo;
    WORD      Unknown2;
    LONG      SendMessageLParam;
    WORD      SendMessageWParam;
    WORD        SendMessageMessage;
    HWND        SendMessageHWnd;
    DWORD      SendMessageResult;
    WORD        QuitFlag;
    int         ExitCode;
    WORD        flags;
    DWORD      Unknown3;
    WORD      ExpWinVersion;
    WORD      SendingHQ;
   WORD      sendmsg_helper1;
    WORD        sendmsg_helper2;
    WORD      PaintCount;
    WORD      TimersCount;
    WORD        ChangeBits;
    WORD        WakeBits;
    WORD        WakeMask;
    WORD        SendMessageResult1;
    WORD        SendMessageResult2;
    WORD        SendMessageResult3;
    WORD      Hook;
   BYTE      Hooks2[30];
   BYTE      MessageArrayStart;
} QUEUE;

//
// Dumps selected fields of a message queue
//

void DumpQueueContents(QUEUE far *queue)
{
   QUEUEMSG far *queuemsg;
   unsigned maxMessages, i;

   maxMessages =
      ( queue->Size - FP_OFF(&queue->MessageArrayStart))
               / sizeof(QUEUEMSG);

   queuemsg = (QUEUEMSG far *) &queue->MessageArrayStart;

   printf("Messages: %u  ReadPtr: %04X  WritePtr: %04X\n",
      queue->NumMessages, queue->ReadPtr, queue->WritePtr);

   printf("WakeBits: ");
   if ( queue->WakeBits & QS_KEY )
      printf("QS_KEY ");
   if ( queue->WakeBits & QS_MOUSE )
      printf("QS_MOUSE ");
   if ( queue->WakeBits & QS_POSTMESSAGE )
      printf("QS_POSTMESSAGE ");
   if ( queue->WakeBits & QS_TIMER )
      printf("QS_TIMER ");
   if ( queue->WakeBits & QS_PAINT )
      printf("QS_PAINT ");
   printf("\n");

   for ( i=0; i < maxMessages; i++ )
   {
      printf(
         "HWnd: %04X  Msg: %04X  WParam: %04X  LParam: %08lX\n",
         queuemsg->hwnd, queuemsg->message,
         queuemsg->wParam, queuemsg->lParam );

      queuemsg++;
   }
   printf("\n");
}

//
// Get a pointer to the application message queue.  Then, puts
// some messages into the queue, and retrieve them.  We display
// the contents of the queue at each state, so that we can see
// the principles involved.
//

void ExamineQueue(void)
{
   QUEUE far *queue;
   MSG msg;

   queue = MK_FP( GetTaskQueue(GetCurrentTask()), 0 );

   if ( !queue )
   {
      printf("Unable to find message queue\n");
      return;
   }

   printf("Here we have an empty queue:\n\n");
   DumpQueueContents(queue);


   printf(
   "We'll now call PostAppMessage() to put some messages in\n"
   "the queue.  Note that the message count goes up, and that\n"
   "QS_POSTMESSAGE is now set:\n\n");

   PostAppMessage(GetCurrentTask(), 0x1234, 0x5678, 0x12345678L);
   PostAppMessage(GetCurrentTask(), 0x2345, 0x6789, 0x12345678L);
   PostAppMessage(GetCurrentTask(), 0x3456, 0x789A, 0x12345678L);
   PostAppMessage(GetCurrentTask(), 0x4567, 0x89AB, 0x12345678L);

   DumpQueueContents(queue);

   printf(
   "We'll now call GetMessage() to remove a message. The\n"
   "message still appears in the message array, but the Read\n"
   "pointer has been incremented.  We also print out the\n"
   "contents of the retrieved message to show that it matches\n"
   "what was in the queue:\n\n");

   GetMessage(&msg, 0, 0, 0);
   DumpQueueContents(queue);

   printf(
   "The message retrieved into the MSG struct:\n"
   "HWnd: %04X  Msg: %04X  WParam: %04X  LParam: %08lX\n\n",
   msg.hwnd, msg.message, msg.wParam, msg.lParam );

   printf(
   "We now call GetMessage 3 more times to get rid of the\n"
   "remaining messages.  Note that the Read and Write ptrs are\n"
   "equal, the QS_POSTMESSAGE flag is no longer set, and the\n"
   "message count field shows 0.  Thus, the queue is considered\n"
   "to be empty:\n\n");

   GetMessage(&msg, 0, 0, 0);
   GetMessage(&msg, 0, 0, 0);
   GetMessage(&msg, 0, 0, 0);
   DumpQueueContents(queue);
}

int main()
{
   // This program uses the message queue format for Windows
   // 3.1.  Abort if running under any other version.

   if ( LOWORD(GetVersion()) != 0x0A03 )
   {
      winio_warn(FALSE, "QUEUE",
         "This program requires Windows 3.1");

      return 1;
   }

   // Turn off repaints.  If we don't do this, the WINIO library
   // will attempt to use the queue while we're in the process of
   // examining it.

   winio_setbusy();
    winio_setpaint(winio_current(), FALSE);

   ExamineQueue();

   // Turn the repaints back on.  This allows WINIO to refresh
   // the display with all the output that was created in
   // ExamineQueue().

    winio_setpaint(winio_current(), TRUE);
    winio_resetbusy();
    winio_home(winio_current());
   return 0;
}













Copyright © 1993, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.