本节将讨论系统如何利用窗口消息在进程之间传送数据。一些窗口消息在其l P a r a m参数中指出了一个内存块的地址。例如, W M _ S E T T E X T消息使用l P a r a m参数作为指向一个以零结尾的字符串的指针,这个字符串为窗口规定了新的文本标题串。考虑下面的调用:
SendMessage(FindWindow(NULL, "Calculator"), WM_SETTEXT, 0, (LPARAM) "A Test Caption");
新标题的字符串包含在调用进程的地址空间里。所以这个在调用进程空间的字符串的地址将传递给l P a r a m参数。当C a l c u l a t o r的窗口的窗口过程收到这个消息时,它要查看l P a r a m参数,并要读取这个以零结尾的字符串,使其成为新的标题。
但l P a r a m中的地址指向调用进程的地址空间中的字符串,而不是C a l c u l a t o r的地址空间。这会发生内存存取违规这种严重问题。但当你执行上面的代码时,你会看到执行是成功的,为什么会是这样?
答案是系统特别要检查W M _ S E T T E X T消息,并用与处理其他消息不同的方法来处理这个消息。当调用S e n d M e s s a g e时,函数中的代码要检查是否要发送一个W M _ S E T T E X T消息。如果是,就将以零结尾的字符串从调用进程的地址空间放入到一个内存映像文件中,该内存映像文件可在进程间共享。然后再发送消息到其他进程的线程。当接收线程已准备好处理W M _ S E T T E X T消息时,它在自己的地址空间中确定包含新的窗口文本标题的共享内存映像文件的位置,再将W M _ S E T T E X T消息派送到相应的窗口过程。在处理完消息之后,内存映像文件被删除。这样做看起来是不是太麻烦了一些。
幸而大多数消息不要求这种类型的处理。仅当这种消息是程序在进程间发送的消息,特别是消息的w P a r a m或l P a r a m参数表示一个指向数据结构的指针时,要做这样的处理。
我们再来看另外一个要求系统特殊处理的例子—— W M _ G E T T E X T消息。假定一个程序包含下面的代码:
char szBuf[200]; SendMessage(FindWindow(NULL, "Calculator"), WM_GETTEXT, sizeof(szBuf), (LPARAM) szBuf);
当内存映像文件被建立时,系统就发送消息来填充它。然后系统再转回到最初调用S e n d M e s s a g e的进程,从共享内存映像文件中将数据复制到s z B u f所指定的缓冲区中,然后从S e n d M e s s a g e调用返回。
对于系统已经知道的消息,发送消息时都可以按相应的方式来处理。如果你要建立自己的(W M _ U S E R+x)消息,并从一个进程向另一个进程的窗口发送,那又会怎么样?系统不知道你要用内存映像文件并在发送消息时改变指针。为此,微软建立了一个特殊的窗口消息,W M _ C O P Y D ATA以解决这个问题:
COPYDATASTRUCT cds; SendMessage(hwndReceiver, WM_COPYDATA, (WPARAM)hwndSender, (LPARAM) &cds);
typedef struct tagCOPYDATASTRUCT { ULONG_PTR dwData; DWORD cbData; PVOID lpData; } COPYDATASTRUCT;
c b D a t a数据成员规定了向另外的进程发送的字节数, l p D a t a数据成员指向要发送的第一个字节。l p D a t a所指向的地址,当然在发送进程的地址空间中。
当S e n d M e s s a g e看到要发送一个W M _ C O P Y D ATA消息时,它建立一个内存映像文件,大小是c b D a t a字节,并从发送进程的地址空间中向这个内存映像文件中复制数据。然后再向目的窗口发送消息。在接收消息的窗口过程处理这个消息时, l P a r a m参数指向已在接收进程地址空间的一个C O P Y D ATA S T R U C T结构。这个结构的l p D a t a成员指向接收进程地址空间中的共享内存映像文件的视图。
关于W M _ C O P Y D ATA消息,应该注意三个重要问题:
• 只能发送这个消息,不能登记这个消息。不能登记一个W M _ C O P Y D ATA消息,因为在接收消息的窗口过程处理完消息之后,系统必须释放内存映像文件。如果登记这个消息,系统不知道这个消息何时被处理,所以也不能释放复制的内存块。
• 系统从另外的进程的地址空间中复制数据要花费一些时间。所以不应该让发送程序中运行的其他线程修改这个内存块,直到S e n d M e s s a g e调用返回。
• 利用W M _ C O P Y D ATA消息,可以实现1 6位和3 2位之间的通信。它也能实现3 2位与6 4位之间的通信。这是使新程序同旧程序交流的便捷方法。注意在Windows 2000和Wi n d o w s9 8上完全支持W M _ C O P Y D ATA。但如果你依然在编写1 6位Wi n d o w s程序, M i c r o s o f eVisual C++ 1.52没有W M _ C O P Y D ATA消息的定义,也没有C O P Y D ATA S T R U C T结构的定义。需要手工添加这些代码:
// 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;
C o p y D a t a示例程序
清单2 6 - 1所列的C o p y D a t a程序(“26 CopyData.exe)说明了如何使用W M _ C O P Y D ATA消息从一个程序向另一个程序发送一个数据块。该程序的源代码和资源文件在本书所附光盘的2 6 - C o p y D a t a目录下。要看它如何工作,至少需要让两个C o p y D a t a的实例运行。每次启动一个C o p y D a t a时,它要显示如图2 6 - 3所示的对话框。
图26-3 CopyData Application 对话框
为了观看从一个程序到另一个程序的数据复制,首先改变D a t a 1和D a t a 2编辑控制框中的文本。然后点击某个Send Data* to Other Wi n d o w s按钮,程序向所有运行的C o p y D a t a的实例发数据。每个实例更新自己的编辑框中的内容来反应新数据。
下面描述C o p y D a t a如何工作。当一个用户点击图2 6 - 3中两个按钮中的某一个时,C o p y D a t a执行下面的动作。
1) 如果用户点击Send Data1 To Other Wi n d o w s按钮,用0来初始化C O P Y D ATA S T R U C T的d w D a t a成员,如果用户点击Send Data2 To Other Wi n d o w s按钮,则用1来初始化d w D a t a成员。
2) 从相应的文本框中求取文本串的长度(按字符数计),并加1(对应一个零结束符)。这个值乘以S i z e o f ( T C H A R ),从字符数转换成字节数。结果存入C O P Y D ATA S T R U C T的c b D a t a成员中。
3) 调用_ a l l o c a分配一个内存块,大小足以容纳编辑框中的字符串加上零结束符。这个块的地址存放在C O P Y D ATA S T R U C T结构的l p D a t a成员中。
4) 从编辑框向分配的内存块复制字符串。
这个时候,一切就绪,准备向其他窗口发送数据。为了确定要向哪个窗口发送W M _ C O P Y D ATA消息,C o p y D a t a调用F i n d Wi n d o w E x函数传递它自己的对话框标题,以便只有其他的C o p y D a t a程序实例才会被枚举。当找到每个实例的窗口时,发送W M _ C O P Y D ATA消息,每个实例更新它的编辑控制框。
清单26-1 CopyData示例程序
/****************************************************************************** Module: CopyData.cpp Notices: Copyright (c) 2000 Jeffrey Richter ******************************************************************************/ #include "..\CmnHdr.h" /* See Appendix A. */ #include <windowsx.h> #include <tchar.h> #include <malloc.h> #include "Resource.h" /// // WindowsX.h doesn't have a prototype for Cls_OnCopyData, so here it is /* BOOL Cls_OnCopyData(HWND hwnd, HWND hwndFrom, PCOPYDATASTRUCT pcds) */ /// BOOL Dlg_OnCopyData(HWND hwnd, HWND hwndFrom, PCOPYDATASTRUCT cds) { Edit_SetText(GetDlgItem(hwnd, cds->dwData ? IDC_DATA2 : IDC_DATA1), (PTSTR) cds->lpData); return(TRUE); } /// BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) { chSETDLGICONS(hwnd, IDI_COPYDATA); // Initialize the edit controls with some test data. Edit_SetText(GetDlgItem(hwnd, IDC_DATA1), TEXT("Some test data")); Edit_SetText(GetDlgItem(hwnd, IDC_DATA2), TEXT("Some more test data")); return(TRUE); } /// void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { case IDCANCEL: EndDialog(hwnd, id); break; case IDC_COPYDATA1: case IDC_COPYDATA2: if (codeNotify != BN_CLICKED) break; HWND hwndEdit = GetDlgItem(hwnd, (id == IDC_COPYDATA1) ? IDC_DATA1 : IDC_DATA2); // Prepare the COPYDATASTRUCT. COPYDATASTRUCT cds; // Indicate which data field we're sending (0=ID_DATA1, 1=ID_DATA2) cds.dwData = (DWORD) ((id == IDC_COPYDATA1) ? 0 : 1); // Get the length (in bytes) of the data block we're sending. cds.cbData = (Edit_GetTextLength(hwndEdit) + 1) * sizeof(TCHAR); // Allocate a block of memory to hold the string. cds.lpData = _alloca(cds.cbData); // Put the edit control's string in the data block. Edit_GetText(hwndEdit, (PTSTR) cds.lpData, cds.cbData); // Get the caption of our window. TCHAR szCaption[100]; GetWindowText(hwnd, szCaption, chDIMOF(szCaption)); // Enumerate through all the top-level windows with the same caption HWND hwndT = NULL; do { hwndT = FindWindowEx(NULL, hwndT, NULL, szCaption); if (hwndT != NULL) { FORWARD_WM_COPYDATA(hwndT, hwnd, &cds, SendMessage); } } while (hwndT != NULL); break; } } /// INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog); chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand); chHANDLE_DLGMSG(hwnd, WM_COPYDATA, Dlg_OnCopyData); } return(FALSE); } /// int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int) { DialogBox(hinstExe, MAKEINTRESOURCE(IDD_COPYDATA), NULL, Dlg_Proc); return(0); } End of File //
//Microsoft Developer Studio generated resource script. // #include "Resource.h" #define APSTUDIO_READONLY_SYMBOLS / // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" / #undef APSTUDIO_READONLY_SYMBOLS / // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 / // // Dialog // IDD_COPYDATA DIALOG DISCARDABLE 38, 36, 220, 42 STYLE WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "CopyData Application" FONT 8, "MS Sans Serif" BEGIN LTEXT "Data&1:",IDC_STATIC,4,4,24,12 EDITTEXT IDC_DATA1,28,4,76,12 PUSHBUTTON "&Send Data1 to other windows",IDC_COPYDATA1,112,4,104, 14,WS_GROUP LTEXT "Data&2:",IDC_STATIC,4,24,24,12 EDITTEXT IDC_DATA2,28,24,76,12 PUSHBUTTON "Send &Data2 to other windows",IDC_COPYDATA2,112,24,104, 14,WS_GROUP END / // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_COPYDATA ICON DISCARDABLE "CopyData.Ico" #ifdef APSTUDIO_INVOKED / // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "Resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED #endif // English (U.S.) resources / #ifndef APSTUDIO_INVOKED / // // Generated from the TEXTINCLUDE 3 resource. // / #endif // not APSTUDIO_INVOKED