窗口间的消息互发
在不同应用程序之间的窗口中可以互发消息,方法是通过SendMessage或者PostMessage函数,它们的用法如下:
invoke PostMessage, hWnd, Msg, wParam, lParam
invoke SendMessage, hWnd, Msg, wParam, lParam
对于不同的Msg,wParam和lParam的含义是不同的,如对于WM_SETTEXT是:
wParam = 0; //未定义,必须为0
lParam = (LPARAM)(LPCTSTR)lpsz; //要设置的字符串地址
想一想就会发现一个问题:Windows中不同应用程序的地址空间是隔离的,全市程序1要用SendMessage调用程序2所属窗口的窗口过程,但程序2窗口过程的代码并不在程序1的地址空间中,那么SendMessage如何调用它呢?其实很简单,当程序1调用SendMessage函数的时候,Windows会先保存wParam和lParam参数并等待,等轮到程序2的时间片的时候再去调用它的窗口过程,并把保存的wParam和lParam参数发给它,等窗口过程返回的时候,Windows记下返回值并等待程序1,这样程序1看上去就像自己直接在调用程序2的窗口过程一样。
但又一个问题出现了:Windows在做“牵线红娘”的时候传递了wParam和lParam以及返回值,如果参数指向一个字符串呢,比如说上面的WM_SETTEXT消息中的lParam指向一个字符串,假设程序1中lParam指向字符串的地址为xxxxxxxx,把这个地址传给程序2的时候,程序2不可能访问到程序1的地址空间,在程序2中xxxxxxxx指向的可能是其他内容,也可能是不可访问的,这又该如何处理呢?
写一个源程序实验一下,用一个程序向另一个程序的窗口发送WM_SETTEXT消息,然后在另一个程序中将接收到的WM_SETTEXT消息的参数显示出来。先来打造接收程序,首先拷贝一份FirstWindows的代码,然后在窗口过程的分支中加上以下代码:
.elseif eax == WM_SETTEXT
invoke wsprintf, addr szBuffer, addr szReceive, lParam, lParam
invoke MessageBox, hWnd, offset szBuffer, addr szCaptionMain, MB_ok
同时在数据段中加上下列定义:
szCaptionMain db ‘Receive Messag’,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表示输出为16进制,%s表示输出字符串等。
%符号和表示格式的d,x和s等字母间可以用数字来指定输出时占用的位长,这时输出的位长不够时函数会用空格填齐。另外,表示位长的数字前可以加0来表示填齐时用“0”而非空格,如%08x表示输出为8位前面用0填齐的16进制数。
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 SendMessage, 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 no 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程序中字符串的地址是00402072h,现在单击“确定”按钮执行SendMessage函数,单击后对话框消失,但接收程序显示出了一个对话框,内容为:
Receive WM_SETTEXT message
param: 0012fflc
text: “Text send to other windows”
可见字符串是正确地传了过来,但地址却不是发送程序的00402072h,这是为何?
答案是Window做“红娘”做到底,它拷贝了WM_SETTEXT消息的lParam参数指向的字符串,并在接收程序的地址空间中开了一块内存放上这个字符串,然后把新的地址值当做lParam传给接收程序,毕竟在WM_SETTEXT消息中,lParam的数值是多少并不重要,重要的是它指向的字符串是否正确。
最后,单击接收程序中的“确定”按钮,发送程序马上弹出一个消息框并显示:SendMessage returned,这是SendMessage函数告诉我们:我回来了!
其实Windows在处理SendMessage和PostMessage的时候要检查消息的类型,并对不同的消息做不同的处理,当消息的参数是一个指针的时候,Windows要把指针指向的内容复制到缓冲区,以便在两个程序的地址空间中传递。
注意:在用户自定义的消息中(WM_USER等)不要在消息参数中传递指针,这只会引发非法访问内存,因为Windows不知道用户的意图,它只会把lParam和wParam当两个普通的数值传递,而不会帮用户把指针指向的内容复制到一个缓冲区中。