Windows核心编程上的,自己看吧,反正我看了好长时间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   0x004Atypedef 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. For an example that demonstrates an excellent use of the WM_COPYDATA message, see the LastMsgBoxInfo sample application presented in Chapter 22.The CopyData Sample Application
The CopyData application ("26 CopyData.exe"), listed in Figure 26-3, demonstrates how to use the WM_COPYDATA message to send a block of data from one application to another. The source code and resource files for the application are in the 26-CopyData directory on the companion CD-ROM. You'll need to have at least two copies of CopyData running to see it work. Each time you start a copy of CopyData, it presents a dialog box that looks like this. To see data copied from one application to another, first change the text in the Data1 and Data2 edit controls. Then click on one of the two Send Data* To Other Windows buttons, and the program sends the data to all the running instances of CopyData. Each instance updates the contents of its own edit box to reflect the new data.The list below describes how CopyData works. When a user clicks on one of the two buttons, CopyData performs the following actions.
Initializes the dwData member of COPYDATASTRUCT with 0 if the user clicked on the Send Data1 To Other Windows button, or with 1 if the user clicked on the Send Data2 To Other Windows button.
Retrieves the length of the text string (in characters) from the appropriate edit box and adds 1 for a zero-terminating character. This value is converted from a number of characters to a number of bytes by multiplying by sizeof(TCHAR). The result is then placed in the cbData member of COPYDATASTRUCT.
Calls _alloca to allocate a block of memory large enough to hold the length of the string in the edit box plus its zero-terminating character. The address of this block is stored in the lpData member of COPYDATASTRUCT.
Copies the text from the edit box into this memory block.At this point, everything is ready to be sent to the other windows. To determine which windows to send the WM_COPYDATA message to, CopyData calls FindWindowEx passing the caption of its dialog box so that only other instances of the CopyData application are enumerated. As each instance's window is found, the WM_COPYDATA message is forwarded so that each instance updates its edit controls.Figure 26-3. The CopyData sample application[Previous] [Next]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. 

解决方案 »

  1.   

    Excellent explanation!
    Thank you very much
      

  2.   

    我用SendMessageCallback, SendNotifyMessage 发送WM_COPYDATA,函数执行失败, GetLastError()返回87 (The parameter is incorrect.  ERROR_INVALID_PARAMETER)
    用SendMessageTimeout发送WM_COPYDATA 成功. 所以,我是否可以认为:SendMessageCallback, SendNotifyMessage 不支持WM_COPYDATA
    SendMessageTimeout支持WM_COPYDATA(即可以自动删除传输用的memory file)请问我的结论是否正确?
      

  3.   

    1、Windows自己负责传输同步
    2、sendmessage肯定是组塞的,可能是你在哪个地方出了问题
      

  4.   

    看了半天,原来还是用的CreateFileMapping,看来这是进程间共享数据的根本办法。
      

  5.   

    如果Windows自己负责SendMessage(WM_COPYDATA,...)传输同步,可能会采取什么样的实现手段?
    另外,Windows核心编程上说SendNotifyMessage发出消息的优先级高于PostMessage, 但比SendMessage低.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.所以如果大量使用SendMessage(WM_COPYDATA,...)向一个UI进程发数据,系统通过SendNotifyMessage 向这个UI进程发送的消息是否有可能得不到及时相应?可能我问的问题比较深, 谢谢大家关注.
      

  6.   

    请问,调试时是否有办法跟踪进入SendMessage,SendMessageTimeout的内部,进行源码级调试?