在前面的内容中,小甲鱼已经为大家介绍过在不同应用程序之间的窗口中是可以互发消息。

方法是通过SendMessage 或者 PostMessage 函数,它们的用法如下:
invoke PostMessage, hWnd, Msg, wParam, lParam
invoke SendMessage, hWnd, Msg, wParam, lParam

注意:对于不同的Msg,wParam和lParam的含义是不同的!

根据”常识”,我们发现一个矛盾:
Windows 中不同应用程序的地址空间是隔离的,假设 程序A 要用 SendMessage 调用 程序B 所属窗口的窗口过程,但 程序B 窗口过程的代码并不在 程序A 的地址空间中,那么 SendMessage 将如何调用到它呢?!
。。。。。。
。。。。。
。。。。
。。。

其实很简单,当程 序A 调用 SendMessage 函数的时候,Windows 会先保存 wParam 和 lParam 参数并等待轮到 程序B 的时间片的时候再去调用它的窗口过程,并将保存的 wParam 和 lParam 参数发给它。

然后等到窗口过程返回的时候,Windows 又记下返回值并继续等待,再等轮到 程序A 的时间片的时候把返回值当做 SendMessage 的返回值传给 程序A。

这样 程序A 看上去就像自己直接在调用 程序B 的窗口过程一样。

刚刚那个问题是小儿科,大家估计都能想到。但又一个问题出现了:
Windows 在做”牵线红娘”的时候传递了 wParam 和 lParam 以及返回值,如果参数指向一个字符串呢?

具体的比如 lParam 指向一个字符串,假设 程序A 中 lParam 指向字符串的地址为 xxxxxxxx,把这个地址传给 程序B 的时候,程序B 不可能访问到 程序A 的地址空间,而且在 程序B 中 xxxxxxxx 指向的又是其他内容,也可能是不可访问的,这又该如何处理呢?

实例验证

我们不如写一个源程序实验一下:
用一个程序向另一个程序的窗口发送WM_SETTEXT 消息,在另一个程序中将接收到的 WM_SETTEXT 消息的参数显示出来。

在演示前,小甲鱼给大家伙再讲讲这个 wsprintf
wsprintf 是 Win32 API 中一个很常用的函数,类似于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 函数的时候,参数的数量取决于格式化字符串中用%号指定的数量,变量列表的数目和格式化串中的%格式一定要一一对应。

那我们开始来写代码:
Send.asm
Receive.asm


上一节课我们演示了两个窗口间进行字符串的传输,但君不见字符串是正确地传了过来,但地址却不是发送程序的参数地址,这是怎么回事呢?

答案是 Windows 做”红娘”做到底,它拷贝了WM_SETTEXT 消息 lParam 指向的字符串,并在接收程序的地址空间中开了一块内存临时存放,然后把新的地址值当做 lParam 传给接收程序。

因为 lParam 的数值是多少事实上并不重要,重要的是它指向的字符串是否正确。

在 WM_SETTEXT 这一类的消息中,Windows 可以将参数所指的字符串传递到目标窗口过程中。

局限性是传递的数据也只限于以 0 作为结尾的字符串。

那如果我们需要在不同窗口间自由地拷贝任意类型的数据,应该怎么办呢?

Windows 为我们提供了一个特殊的窗口消息 – WM_COPYDATA

WM_COPYDATA 消息用一个 COPYDATASTRUCT 结构来描述要拷贝的数据长度和位置。

COPYDATASTRUCT STRUCT
dwData DWORD ? ; 附加字段
cbData DWORD ? ; 数据长度
lpData DWORD ? ; 数据位置指针
COPYDATASTRUCT ENDS

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 所指的数据拷贝到共享内存中并定位该共享内存新的地址覆盖到 lpData 字段中。
最后将经过处理的 COPYDATASTRUCT 结构发送给目标窗口过程。再最后,目标窗口过程返回后,Windows 自动给释放共享内存,SendMessage 函数返回。


SendMessage和PostMessage的区别

从逻辑上看,SendMessage 函数相当于直接调用其他窗口的窗口过程来处理某个消息,并等待窗口过程的返回。在函数返回后,目标窗口过程必定已经处理了该消息。

PostMessage 函数则将消息放入目标窗口的消息队列中并直接返回。函数返回后,目标窗口过程可能还没有处理到该消息。

对于普通的消息来说,两个函数除了在处理速度上有所区别外,其他的表现都一模一样。

但是对于 WM_SETTEXT, WM_COPYDATA 等在参数中用到指针的消息来说,两者就有所不同了。

我们尝试一下把 SendMessage2 的 SendMessage 改为 PostMessage:SendMessage3