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当两个普通的数值传递,而不会帮用户把指针指向的内容复制到一个缓冲区中。

上页:第04章 第一个窗口程序 · 4.2 分析窗口程序(5) 下页:第04章 第一个窗口程序 · 4.4 实验(1)

第04章 第一个窗口程序

版权所有 © 中山市飞娥软件工作室 证书:粤ICP备09170368号