Sending Data with Messages
In this section, we'll examine how the system transfers data between processes using window messages. Some window messages specify the address of a block of memory in their lParam parameter. For example, the WM_SETTEXT message uses the lParam parameter as a pointer to a zero-terminated string that identifies the new text for the window. Consider the following call:
SendMessage(FindWindow(NULL, "Calculator"), WM_SETTEXT,
0, (LPARAM) "A Test Caption");
This call seems harmless enough—it determines the window handle of the Calculator application's window and attempts to change its caption to A Test Caption. But let's take a closer look at what happens here.
The string of the new title is contained in your process's address space. So the address of this string in your process space will be passed as the lParam parameter. When the window procedure for Calculator's window receives this message, it looks at the lParam parameter and attempts to manipulate what it thinks is a zero-terminated string in order to make it the new title.
But the address in lParam points to a string in your process's address space—not in Calculator's address space. This is a big problem because a memory access violation is sure to occur. But if you execute the line above, you'll see that it works successfully. How can this be?
The answer is that the system looks specifically for the WM_SETTEXT message and handles it differently from the way it handles most other messages. When you call SendMessage, the code in the function checks whether you are trying to send a WM_SETTEXT message. If you are, it packs the zero-terminated string from your address space into a memory-mapped file that it is going to share with the other process. Then it sends the message to the thread in the other process. When the receiving thread is ready to process the WM_SETTEXT message, it determines the location, in its own address space, of the shared memory-mapped file that contains a copy of the new window text. The lParam parameter is initialized to point to this address, and the WM_SETTEXT message is dispatched to the appropriate window procedure. After the message is processed, the memory-mapped file is destroyed. Boy, doesn't this seem like a lot of work?
Fortunately, most messages don't require this type of processing—it takes place only when an application sends interprocess messages. Special processing such as this has to be performed for any message whose wParam or lParam parameters represent a pointer to a data structure.
Let's look at another case that requires special handling by the system—the WM_GETTEXT message. Suppose your application contains the following code:
char szBuf[200];
SendMessage(FindWindow(NULL, "Calculator"), WM_GETTEXT,
sizeof(szBuf), (LPARAM) szBuf);
The WM_GETTEXT message requests that Calculator's window procedure fill the buffer pointed to by szBuf with the title of its window. When you send this message to a window in another process, the system must actually send two messages. First the system sends a WM_GETTEXTLENGTH message to the window. The window procedure responds by returning the number of characters required to hold the window's title. The system can use this count to create a memory-mapped file that will end up being shared between the two processes.
Once the memory-mapped file has been created, the system can send the WM_GETTEXT message to fill it. Then the system switches back to the process that called SendMessage in the first place, copies the data from the shared memory-mapped file into the buffer pointed to by szBuf, and returns from the call to SendMessage.
Well, all this is fine and good if you are sending messages that the system is aware of. But what if you create your own (WM_USER + x) message that you want to send from one process to a window in another? The system will not know that you want it to use memory-mapped files and to update pointers when sending. However, Microsoft has created a special window message, WM_COPYDATA, for exactly this purpose:
COPYDATASTRUCT cds;
SendMessage(hwndReceiver, WM_COPYDATA,
(WPARAM) hwndSender, (LPARAM) &cds);
COPYDATASTRUCT is a structure defined in WinUser.h, and it looks like this:
typedef struct tagCOPYDATASTRUCT {
ULONG_PTR dwData;
DWORD cbData;
PVOID lpData;
} COPYDATASTRUCT;
When you're ready to send some data to a window in another process, you must first initialize the COPYDATASTRUCT structure. The dwData member is reserved for your own use. You can place any value in it. For example, you might have occasion to send different types or categories of data to the other process. You can use this value to indicate the content of the data you are sending.
The cbData member specifies the number of bytes that you want to transfer to the other process, and the lpData member points to the first byte of the data. The address pointed to by lpData is, of course, in the sender's address space.
When SendMessage sees that you are sending a WM_COPYDATA message, it creates a memory-mapped file cbData bytes in size and copies the data from your address space to the memory-mapped file. It then sends the message to the destination window. When the receiving window procedure processes this message, the lParam parameter points to a COPYDATASTRUCT that exists in the address space of the receiving process. The lpData member of this structure points to the view of the shared memory-mapped file in the receiving process's address space.
You should remember three important things about the WM_COPYDATA message:
Always send this message; never post it. You can't post a WM_COPYDATA message because the system must free the memory-mapped file after the receiving window procedure has processed the message. If you post the message, the system doesn't know when the WM_COPYDATA message is processed, and therefore it can't free the copied block of memory.
It takes some time for the system to make a copy of the data in the other process's address space. This means that you shouldn't have another thread that modifies the contents of the memory block running in the sending application until the call to SendMessage returns.
The WM_COPYDATA message allows a 16-bit application to communicate with a 32-bit application and vice versa. It also allows a 32-bit application to talk to a 64-bit application and vice versa. This is an incredibly easy way to have newer applications talk to older applications. Also note that WM_COPYDATA is fully supported on Windows 2000 and Windows 98. Unfortunately, if you are still writing 16-bit Windows applications, Microsoft Visual C++ 1.52 does not have a definition for the WM_COPYDATA message or the COPYDATASTRUCT structure. You will need to add them manually:
// Manually include this in your 16-bit Windows source code.
#define WM_COPYDATA 0x004A
typedef VOID FAR* PVOID;
typedef struct tagCOPYDATASTRUCT {
DWORD dwData;
DWORD cbData;
PVOID lpData;
} COPYDATASTRUCT, FAR* PCOPYDATASTRUCT;
The WM_COPYDATA message is an incredible device that could save many developers hours of time when trying to solve interprocess communication problems. It's a shame it's not used more frequently.
Sending Messages to a Window
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.
posted on 2010-05-08 23:30
RUI 阅读(2455)
评论(0) 编辑 收藏 引用 所属分类:
Windows Programming