Sending Messages to a Window

[Previous] [Next]

Window messages can be sent directly to a window procedure by using the SendMessage function:

 LRESULT SendMessage( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 

The window procedure will process the message. Only after the message has been processed will SendMessage return to the caller. Because of its synchronous nature, SendMessage is used more frequently than either PostMessage or PostThreadMessage. The calling thread knows that the window message has been completely processed before the next line of code executes.

Here is how SendMessage works. If the thread calling SendMessage is sending a message to a window created by the same thread, SendMessage is simple: it just calls the specified window's window procedure as a subroutine. When the window procedure is finished processing the message, it returns a value back to SendMessage. SendMessage returns this value to the calling thread.

However, if a thread is sending a message to a window created by another thread, the internal workings of SendMessage are far more complicated.1 Windows requires that the thread that created the window process the window's message. So if you call SendMessage to send a message to a window created by another process, and therefore to another thread, your thread cannot possibly process the window message because your thread is not running in the other process's address space and therefore does not have access to the window procedure's code and data. In fact, your thread is suspended while the other thread is processing the message. So in order to send a window message to a window created by another thread, the system must perform the actions I'll discuss next.

First, the sent message is appended to the receiving thread's send-message queue, which has the effect of setting the QS_SENDMESSAGE flag (which I'll discuss later) for that thread. Second, if the receiving thread is already executing code and isn't waiting for messages (on a call to GetMessage, PeekMessage, or WaitMessage), the sent message can't be processed—the system won't interrupt the thread to process the message immediately. When the receiving thread is waiting for messages, the system first checks to see whether the QS_SENDMESSAGE wake flag is set, and if it is, the system scans the list of messages in the send-message queue to find the first sent message. It is possible that several sent messages could pile up in this queue. For example, several threads could each send a message to a single window at the same time. When this happens, the system simply appends these messages to the receiving thread's send-message queue.

When the receiving thread is waiting for messages, the system extracts the first message in the send-message queue and calls the appropriate window procedure to process the message. If no more messages are in the send-message queue, the QS_SENDMESSAGE wake flag is turned off. While the receiving thread is processing the message, the thread that called SendMessage is sitting idle, waiting for a message to appear in its reply-message queue. After the sent message is processed, the window procedure's return value is posted to the sending thread's reply-message queue. The sending thread will now wake up and retrieve the return value contained inside the reply message. This return value is the value that is returned from the call to SendMessage. At this point, the sending thread continues execution as normal.

While a thread is waiting for SendMessage to return, it basically sits idle. It is, however, allowed to perform one task: if another thread in the system sends a message to a window created by a thread that is waiting for SendMessage to return, the system will process the sent message immediately. The system doesn't have to wait for the thread to call GetMessage, PeekMessage, or WaitMessage in this case.

Because Windows uses this method to handle the sending of interthread messages, it's possible that your thread could hang. For example, let's say that the thread processing the sent message has a bug and enters an infinite loop. What happens to the thread that called SendMessage? Will it ever be resumed? Does this mean that a bug in one application can cause another application to hang? The answer is yes!

Four functions—SendMessageTimeout, SendMessageCallback, SendNotifyMessage, and ReplyMessage—allow you to write code defensively to protect yourself from this situation. The first function is SendMessageTimeout:

 LRESULT SendMessageTimeout( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT fuFlags, UINT uTimeout, PDWORD_PTR pdwResult); 

The SendMessageTimeout function allows you to specify the maximum amount of time you are willing to wait for another thread to reply to your message. The first four parameters are the same parameters that you pass to SendMessage. For the fuFlags parameter, you can pass SMTO_NORMAL (defined as 0), SMTO_ABORTIFHUNG, SMTO_BLOCK, SMTO_NOTIMEOUTIFNOTHUNG, or a combination of these flags.

The SMTO_ABORTIFHUNG flag tells SendMessageTimeout to check whether the receiving thread is in a hung state2 and, if so, to return immediately. The SMTO_NOTIMEOUTIFNOTHUNG flag causes the function to ignore the timeout value if the receiving thread is not hung. The SMTO_BLOCK flag causes the calling thread not to process any other sent messages until SendMessageTimeout returns. The SMTO_NORMAL flag is defined as 0 in WinUser.h; this is the flag to use if you don't specify any combination of the other flags.

Earlier in this section I said that a thread could be interrupted while waiting for a sent message to return so that it can process another sent message. Using the SMTO_BLOCK flag stops the system from allowing this interruption. You should use this flag only if your thread could not process a sent message while waiting for its sent message to be processed. Using SMTO_BLOCK could create a deadlock situation until the timeout expires—for example, if you send a message to another thread and that thread needs to send a message to your thread. In this case, neither thread can continue processing and both threads are forever suspended.

The uTimeout parameter specifies the number of milliseconds you are willing to wait for the reply message. If the function is successful, TRUE is returned and the result of the message is copied into the buffer whose address you specify in the pdwResult parameter.

By the way, this function is prototyped incorrectly in the header file of WinUser.h. The function should be prototyped simply as returning a BOOL since the LRESULT is actually returned via a parameter to the function. This raises some problems because SendMessageTimeout will return FALSE if you pass an invalid window handle or if it times out. The only way to know for sure why the function failed is by calling GetLastError. However, GetLastError will be 0 (ERROR_SUCCESS) if the function fails because of a timeout. If you pass an invalid handle, GetLastError will be 1400 (ERROR_INVALID_WINDOW_HANDLE).

If you call SendMessageTimeout to send a message to a window created by the calling thread, the system simply calls the window procedure and places the return value in pdwResult. Because all processing must take place with one thread, the code following the call to SendMessageTimeout cannot start executing until after the message has been processed.

The second function that can help send interthread messages is SendMessageCallback:

 BOOL SendMessageCallback( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, SENDASYNCPROC pfnResultCallBack, ULONG_PTR dwData); 

Again, the first four parameters are the same as those used by the SendMessage function. When a thread calls SendMessageCallback, the function sends the message off to the receiving thread's send-message queue and immediately returns so that your thread can continue processing. When the receiving thread has finished processing the message, a message is posted to the sending thread's reply-message queue. Later, the system notifies your thread of the reply by calling a function that you write using the following prototype:

 VOID CALLBACK ResultCallBack( HWND hwnd, UINT uMsg, ULONG_PTR dwData, LRESULT lResult); 

You must pass the address to this function as the pfnResultCallBack parameter of SendMessageCallback. When this function is called, it is passed the handle of the window that finished processing the message and the message value in the first two parameters. The third parameter, dwData, will always be the value that you passed in the dwData parameter to SendMessageCallback. The system simply takes whatever you specify here and passes it directly to your ResultCallBack function. The last parameter passed to your ResultCallBack function is the result from the window procedure that processed the message.

Because SendMessageCallback returns immediately when performing an interthread send, the callback function is not called as soon as the receiving thread finishes processing the message. Instead, the receiving thread posts a message to the sending thread's reply-message queue. The next time the sending thread calls GetMessage, PeekMessage, WaitMessage, or one of the SendMessage* functions, the message is pulled from the reply-message queue and your ResultCallBack function is executed.

The SendMessageCallback function has another use. Windows offers a method by which you can broadcast a message to all the existing overlapped windows in the system by calling SendMessage and passing HWND_BROADCAST (defined as -1) as the hwnd parameter. Use this method only to broadcast a message whose return value you aren't interested in, because the function can return only a single LRESULT. But by using the SendMessageCallback function, you can broadcast a message to every overlapped window and see the result of each. Your ResultCallBack function will be called with the result of every window processing the message.

If you call SendMessageCallback to send a message to a window created by the calling thread, the system immediately calls the window procedure, and then, after the message is processed, the system calls the ResultCallBack function. After the ResultCallBack function returns, execution begins at the line following the call to SendMessageCallback.

The third function that can help send interthread messages is SendNotifyMessage:

 BOOL SendNotifyMessage( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 

SendNotifyMessage places a message in the send-message queue of the receiving thread and returns to the calling thread immediately. This should sound familiar because it is exactly what the PostMessage function does. However, SendNotifyMessage differs from PostMessage in two ways.

First, if SendNotifyMessage sends a message to a window created by another thread, the sent message has higher priority than posted messages placed in the receiving thread's queue. In other words, messages that the SendNotifyMessage function places in a queue are always retrieved before messages that the PostMessage function posts to a queue.

Second, when you are sending a message to a window created by the calling thread, SendNotifyMessage works exactly like the SendMessage function: SendNotifyMessage doesn't return until the message has been processed.

As it turns out, most messages sent to a window are used for notification purposes; that is, the message is sent because the window needs to be aware that a state change has occurred so that it can perform some processing before you carry on with your work. For example, WM_ACTIVATE, WM_DESTROY, WM_ENABLE, WM_SIZE, WM_SETFOCUS, and WM_MOVE (to name just a few) are all notifications that are sent to a window by the system instead of being posted. However, these messages are notifications to the window; the system doesn't have to stop running so that the window procedure can process these messages. In contrast, when the system sends a WM_CREATE message to a window, the system must wait until the window has finished processing the message. If the return value is -1, the window is not created.

The fourth function that can help in sending interthread messages is ReplyMessage:

 BOOL ReplyMessage(LRESULT lResult); 

This function is different from the three functions we just discussed. Whereas SendMessageTimeout, SendMessageCallback, and SendNotifyMessage are used by the thread sending a message to protect itself from hanging, ReplyMessage is called by the thread receiving the window message. When a thread calls ReplyMessage, it is telling the system that it has completed enough work to know the result of the message and that the result should be packaged up and posted to the sending thread's reply-message queue. This allows the sending thread to wake up, get the result, and continue executing.

The thread calling ReplyMessage specifies the result of processing the message in the lResult parameter. After ReplyMessage is called, the thread that sent the message resumes, and the thread processing the message continues to process the message. Neither thread is suspended; both can continue executing normally. When the thread processing the message returns from its window procedure, any value that it returns is simply ignored.

The problem with ReplyMessage is that it has to be called from within the window procedure that is receiving the message and not by the thread that called one of the Send* functions. So you are better off writing defensive code by replacing your calls to SendMessage with one of the three Send* functions discussed previously instead of relying on the implementer of a window procedure to make calls to ReplyMessage.

You should also be aware that ReplyMessage does nothing if you call it while processing a message sent from the same thread. In fact, this is what ReplyMessage's return value indicates. ReplyMessage returns TRUE if you call it while you are processing an interthread send and FALSE if you are processing an intrathread send.

At times, you might want to know if you are processing an interthread or an intrathread sent message. You can find this out by calling InSendMessage:

 BOOL InSendMessage(); 

The name of this function does not accurately explain what it does. At first glance, you would think that this function returns TRUE if the thread is processing a sent message and FALSE if it's processing a posted message. You would be wrong. The function returns TRUE if the thread is processing an interthread sent message and FALSE if it is processing an intrathread sent or posted message. The return values of InSendMessage and ReplyMessage are identical.

There is another function that you can call to determine what type of message your window procedure is processing:

 DWORD InSendMessageEx(PVOID pvReserved); 

When you call this function, you must pass NULL for the pvReserved parameter. The function's return value indicates what type of message you are processing. If the return value is ISMEX_NOSEND (defined as 0), the thread is processing an intrathread sent or posted message. If the return value is not ISMEX_NOSEND, it is a combination of the bit flags described in the following table.

Flag Description
ISMEX_SEND The thread is processing an interthread sent message sent using either the SendMessage or SendMessageTimeout function. If the ISMEX_REPLIED flag is not set, the sending thread is blocked waiting for the reply.
ISMEX_NOTIFY The thread is processing an interthread sent message sent using the SendNotifyMessage function. The sending thread is not waiting for a reply and is not blocked.
ISMEX_CALLBACK The thread is processing an interthread sent message sent using the SendMessageCallback function. The sending thread is not waiting for a reply and is not blocked.
ISMEX_REPLIED The thread is processing an interthread sent message and has already called ReplyMessage. The sending thread is not blocked.



Programming Applications for Microsoft Windows
Programming Applications for Microsoft Windows (Microsoft Programming Series)
ISBN: 1572319968
EAN: 2147483647
Year: 1999
Pages: 193

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