|
马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
本帖最后由 兰陵月 于 2018-3-12 17:10 编辑
4.3 窗口间的通信
4.3.1 窗口间的消息互发
在介绍消息循环的时候,已经知道在不同的应用程序之间的窗口可以互发消息(如图4.4所示),方法是通过SendMessage或者PostMessage函数,这两个函数的使用语法是相同的:
invoke PostMessage,hWnd,Msg,wParam,lParm
invoke SendMessage,hWnd,Msg,wParam,lParam
对于不同的Msg,wParam和lParam的含义是不同的,如对于WM_SETTEXT是:
wParam = 0 ;未定义,必须为0。
lParam = (LPARAM)(LPCTSTR)lpsz ;要设置的字符串地址。
想一想就会发现一个问题:Windows中不同应用程序的地址空间是隔离的(如图1.6所示),假设程序1要用SendMessage调用程序2所属窗口的窗口过程,但程序2窗口过程的代码并不在程序1的地址空间中,那么SendMessage如何调用它呢?其实很简单,当程序1调用SendMessage函数的时候,Windows会先保存wParam和lParam参数并等待,等轮到程序2的时间片的时候再去调用它的窗口过程,并把保存的wParam和lParam参数发给它,等窗口过程返回的时候,Windows记下返回值并等待,再等轮到程序1的时间片的时候就把返回值当做SendMessage的返回值传给程序1,这样程序1看上去就像自己直接在调用程序2的窗口过程一样。
但又一个问题出现了:Windows在做“牵线红娘”的时候传递了wParam和lParam,以及返回值,如果参数指向一个字符串呢,比如说上面的WM_SETTEXT消息中的lParam指向一个字符串,假设程序1中lParam指向字符串的地址为xxxxxxxx,把这个地址传给程序2的时候,程序2不可能访问到程序1的地址空间,在程序2中xxxxxxxx指向的可能是其他内容,也可能是不可访问的,这又该如何处理呢?
写一个源程序实验一下,用一个程序向另一个程序的窗口发送WM_SETTEXT消息,然后在另一个程序中将接收到的WM_SETTEXT消息的参数显示出来。先来打造接收程序,首先拷贝一份FirstWindow的代码,然后在窗口过程的分支中加上以下代码:
.elseif eax == WM_SETTEXT
invoke wsprintf,addr szBuffer,addr szReceive,lParam,lParam
invoke MessageBox,hWnd,offset szBuffer,addr szCaptionMain,MB_OK
同时在数据段中加上下列定义:
szCaptionMain db 'Receive Message',0
szReceive db 'Receive WM_SETTEXT message',0dh,0ah
db 'param: %08x',0dh,0ah
db 'text: "%s"',0dh,0ah,0
在这里,要提及Win32 API中一个很常用的函数wsprintf,这是一个字符串格式化函数,可以将数值按指定格式翻译成字符串,类似于C语言中的printf函数,它的原型是这样的:
int wsprintf(
LPTSTR lpOut, //输出缓冲区地址
LPCTSTR lpFmt, //格式化串地址
… //变量列表
);
变量列表的数目由格式化字符串规定,wsprintf处理格式化字符串,遇到普通的字符则直接拷贝到输出,遇到%字符则代表有一个变量,%后面不同的字母表示不同的输出格式,如%d表示输出为整数,%x表示输出为十六进制,%s表示输出字符串等。
%符号和表示格式的d,x和s等字母间可以用数字来指定输出时占用的位长,这时如果输出的位长不够时函数会用空格填齐。另外,表示位长的数字前可以加0来表示填齐时用“0”而非空格,如%08x表示输出为8位前面用0填齐的十六进制数。wsprintf是Win32 API中唯一一个参数数量不定的函数,使用wsprintf函数的时候,参数的数量取决于格式化字符串中用%号指定的数量,变量列表的数目和格式化串中的%格式一定要一一对应,比如,例子中szReceive中有两个%号定义,那么后面就要额外跟两个参数:
invoke wsprintf,addr szBuffer,addr szReceive,lParam,lParam
这条语句将lParam的数值,以及lParam的字符串按照szReceive格式化串定义的格式转换,并将结果存放到szBuffer中,然后程序将szBuffer中的内容在一个消息框中显示出来:
invoke MessageBox,hWnd,offset szBuffer,addr szCaptionMain,MB_OK
接收程序写好了,现在来写一个发送程序:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
hWnd dd ?
szBuffer db 256 dup (?)
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.const
szCaption db 'SendMessage',0
szStart db 'Press OK to start Send Message,Param:%08x!',0
szReturn db 'SendMessage returned!',0
szDestClass db 'MyClass',0
szText db 'Text send to other windows',0
szNotFound db 'Receive Message Window not found!',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
start:
invoke FindWindow,addr szDestClass,NULL
.if eax
mov hWnd,eax
invoke wsprintf,addr szBuffer,addr szStart,addr szText
invoke MessageBox,NULL,offset szBuffer,offset szCaption,MB_OK
invoke SendMessage,hWnd,WM_SETTEXT,0,addr szText
invoke MessageBox,NULL,offset szReturn,offset szCaption,MB_OK
.else
invoke MessageBox,NULL,offset szNotFound,offset szCaption,MB_OK
.endif
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
在这个程序中,首先用FindWindow函数找到接收窗口的句柄,FindWindow函数的使用方法是:
invoke FindWindow,lpClassName,lpWindowName
.if eax
mov hWin,eax
.endif
两个参数都指向字符串,lpClassName指向需要寻找的窗口的窗口类,lpWindowName指向需要寻找窗口的窗口标题,如果目标窗口存在的话,函数的返回值是找到的窗口句柄,否则函数返回0。
用接收窗口的窗口类当做参数寻找窗口,如果没有找到,显示“Receive Message Window not found”,找到的话则把“Text send to other windows”字符串的地址当做WM_SETTEXT消息的参数用SendMessage发送给接收窗口。
好,现在开始发送,首先执行Receive.exe,窗口出来了,然后执行Send.exe,屏幕上出现一个对话框:Press OK to start SendMessage,param:00402072,表示在Send程序中字符串的地址是00402072,现在单击“确定”按钮执行SendMessage函数,单击后对话框消失,但接收程序显示出了一个对话框,内容为:
Receive WM_SETTEXT message
param:0008e2c8;注:该地址在执行时可能有所不同。
text:“Text send to other windows”
可见字符串是正确地传了过来,但地址却不是发送程序的00402072,这是怎么回事呢?
其实Windows在处理SendMessage的时候要检查消息的类型,并对不同的消息做不同的处理,当消息的参数是一个普通的32位数时,仅仅将该数值传递给目标窗口的过程;而当消息的参数是一个指针的时候,Windows对指针指向的内容进行了一些处理,以便数据能够正常地传递到目标进程中。
Windows首先创建一块共享内存,并将WM_SETTEXT消息lParam指向的字符串拷贝到该内存中,然后再发送消息到其他进程,并将共享内存在目标进程中的地址发送给目标窗口过程,目标窗口过程处理完消息后,函数返回,共享内存被释放。共享内存使用的是第10章介绍的内存映射文件技术,相当于用图1.6的方法将同一块物理内存映射到不同进程的不同线性地址上去。
虽然当消息传递到目标窗口过程的时候lParam的取值会有所变化,但在WM_SETTEXT消息中,lParam的数值是多少并不重要,重要的是它指向的字符串是否正确。
最后单击Receive程序中的“确定”按钮,Send程序马上会弹出一个消息框并显示:SendMessage returned,这是SendMessage函数告诉我们:我回来了!
在用户自定义的消息中(WM_USER等),不要在消息参数中传递指针,这只会引发非法访问内存,因为Windows不知道用户的意图,它只会把lParam和wParam当两个普通的数值传递,而不会帮用户把指针指向的内容复制到一块共享内存中。
4.3.2 在窗口间传递数据
在WM_SETTEXT这一类的消息中,Windows可以将参数所指的字符串传递到目标窗口过程中,但是这些消息都有它们的本职工作,并且传递的数据也只限于以0结尾的字符串。为了能够在不同进程窗口间自由拷贝任意类型的数据,Windows提供了一个特殊的窗口消息WM_COPYDATA。
WM_COPYDATA消息用一个COPYDATASTRUCT结构来描述要拷贝的数据的长度和位置:
COPYDATASTRUCT STRUCT
dwData DWORD ? ;附加字段
cbData DWORD ? ;数据长度
lpData DWORD ? ;数据位置指针
COPYDATASTRUCT ENDS
其中的dwData字段是一个备用的字段,可以存放任何值,例如,读者有可能向另外的进程发送数据的同时用一个数字来说明数据的类型,那么就可以把这个字段用上去;cbData字段规定了发送的字节数,lpData字段是指向待发送数据的指针。填充好数据结构后,用SendMessage函数就可以将数据发送给目标窗口过程:
.data
stCopyData COPYDATASTRUCT <>
.code
……
invoke SendMessage,hDestWnd,WM_COPYDATA,hWnd,addr stCopyData
例句中的hDestWnd为目标窗口句柄;wParam指定为hWnd,是当前窗口的句柄;lParam指向已经填充完毕的COPYDATASTRUCT结构。
Windows收到WM_COPYDATA消息后,会根据cbData字段的长度创建一块共享内存,并把lpData所指的数据拷贝到共享内存中,然后定位该共享内存在目标进程中的地址,把该地址作为新的地址添加到COPYDATASTRUCT结构的lpData字段中,最后将经过处理的COPYDATASTRUCT结构发送给目标窗口过程,目标窗口过程就可以根据结构中的字段来定位数据了。目标窗口过程返回后,Windows释放掉共享内存,SendMessage函数返回。
4.3.3 SendMessage与PostMessage函数的区别
从逻辑上看,SendMessage函数相当于直接调用其他窗口的窗口过程来处理某个消息,并等待窗口过程的返回,在函数返回后,目标窗口过程必定已经处理了该消息。PostMessage函数则将消息放入目标窗口的消息队列中并直接返回,函数返回后,目标窗口过程可能还没有处理到该消息。
对于普通的消息来说,两个函数除了在处理速度上有所区别外,其他的表现都一模一样,但是对于WM_SETTEXT,WM_COPYDATA等在参数中用到指针的消息来说,两者就有所不同了。读者可以尝试将前面例子的SendMessage函数改为PostMessage函数,就会发现Receive程序根本不会接收到WM_SETTEXT或者WM_COPYDATA消息。事实上,当消息参数中用到指针时,用PostMessage函数来发送消息都不会成功,PostMessage的参考文档中明确地说明,该函数不能用于任何参数中用到指针的消息。
|
|