WIN32汇编语言教程:第04章 第一个窗口程序 · 4.3 窗口间的消息互发
在前面的内容中,已经知道在不同应用程序之间的窗口中可以互发消息(如图4.4所示),方法是通过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.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消息的参数显示出来。先来打造接收程序,首先拷贝一份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 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表示输出为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 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发送给接收窗口。两个程序的源代码可以在所附带光盘的Chapter04\SendMessage目录中找到。
好!现在发送开始,首先执行Receive.exe,窗口出来了,然后执行Send.exe,屏幕上出现一个对话框:Press OK to start SendMessage, param: 00402072,表示在Send程序中字符串的地址是00402072h,现在单击“确定”按钮执行SendMessage函数,单击后对话框消失,但接收程序显示出了一个对话框,内容为:
Receive WM_SETTEXT message param: 0012ff1c text: "Text send to other windows"
可见字符串是正确地传了过来,但地址却不是发送程序的00402072h,这是怎么回事呢?答案是Windows做“红娘”做到底,它拷贝了WM_SETTEXT消息lParam指向的字符串,并在接收程序的地址空间中开了一块内存放上这个字符串,然后把新的地址值当做lParam传给接收程序,毕竟在WM_GETTEXT消息中,lParam的数值是多少并不重要,重要的是它指向的字符串是否正确。
最后,单击Receive 程序中的“确定”按钮,Send程序马上会弹出一个消息框并显示:SendMessage returned,这是SendMessage函数告诉我们:我回来了!
其实Windows在处理SendMessage和PostMessage的时候要检查消息的类型,并对不同的消息做不同的处理,当消息的参数是一个指针的时候,Windows要把指针指向的内容复制到缓冲区,以便在两个程序的地址空间中传递。
在用户自定义的消息中(WM_USER等)不要在消息参数中传递指针,这只会引发非法访问内存,因为Windows不知道用户的意图,它只会把lParam和wParam当两个普通的数值传递,而不会帮用户把指针指向的内容复制到一个缓冲区中。