窗口間的消息互發(fā)
在不同應(yīng)用程序之間的窗口中可以互發(fā)消息,方法是通過SendMessage或者PostMessage函數(shù),它們的用法如下:
invoke PostMessage, hWnd, Msg, wParam, lParam
invoke SendMessage, hWnd, Msg, wParam, lParam
對于不同的Msg,wParam和lParam的含義是不同的,如對于WM_SETTEXT是:
wParam = 0; //未定義,必須為0
lParam = (LPARAM)(LPCTSTR)lpsz; //要設(shè)置的字符串地址
想一想就會(huì)發(fā)現(xiàn)一個(gè)問題:Windows中不同應(yīng)用程序的地址空間是隔離的,全市程序1要用SendMessage調(diào)用程序2所屬窗口的窗口過程,但程序2窗口過程的代碼并不在程序1的地址空間中,那么SendMessage如何調(diào)用它呢?其實(shí)很簡單,當(dāng)程序1調(diào)用SendMessage函數(shù)的時(shí)候,Windows會(huì)先保存wParam和lParam參數(shù)并等待,等輪到程序2的時(shí)間片的時(shí)候再去調(diào)用它的窗口過程,并把保存的wParam和lParam參數(shù)發(fā)給它,等窗口過程返回的時(shí)候,Windows記下返回值并等待程序1,這樣程序1看上去就像自己直接在調(diào)用程序2的窗口過程一樣。
但又一個(gè)問題出現(xiàn)了:Windows在做“牽線紅娘”的時(shí)候傳遞了wParam和lParam以及返回值,如果參數(shù)指向一個(gè)字符串呢,比如說上面的WM_SETTEXT消息中的lParam指向一個(gè)字符串,假設(shè)程序1中lParam指向字符串的地址為xxxxxxxx,把這個(gè)地址傳給程序2的時(shí)候,程序2不可能訪問到程序1的地址空間,在程序2中xxxxxxxx指向的可能是其他內(nèi)容,也可能是不可訪問的,這又該如何處理呢?
寫一個(gè)源程序?qū)嶒?yàn)一下,用一個(gè)程序向另一個(gè)程序的窗口發(fā)送WM_SETTEXT消息,然后在另一個(gè)程序中將接收到的WM_SETTEXT消息的參數(shù)顯示出來。先來打造接收程序,首先拷貝一份FirstWindows的代碼,然后在窗口過程的分支中加上以下代碼:
.elseif eax == WM_SETTEXT
invoke wsprintf, addr szBuffer, addr szReceive, lParam, lParam
invoke MessageBox, hWnd, offset szBuffer, addr szCaptionMain, MB_ok
同時(shí)在數(shù)據(jù)段中加上下列定義:
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中一個(gè)很常用的函數(shù)wsprintf,這是一個(gè)字符串格式化函數(shù),可以將數(shù)值按指定格式翻譯成字符串,類似于C語言中的printf函數(shù),它的原型是這樣的:
int wsprintf (
LPTSTR lpOut, //輸出緩沖地址
LPCTSTR lpFmt //格式化串地址
… //變量列表
);
變量列表的數(shù)目由格式化字符串規(guī)定,wsprintf處理格式化字符串,遇到普通的字符則直接拷貝到輸出,遇到%字符則代表有一個(gè)變量,%后面不同的字母表示不同的輸出格式,如%d表示輸出為整數(shù),%x表示輸出為16進(jìn)制,%s表示輸出字符串等。
%符號和表示格式的d,x和s等字母間可以用數(shù)字來指定輸出時(shí)占用的位長,這時(shí)輸出的位長不夠時(shí)函數(shù)會(huì)用空格填齊。另外,表示位長的數(shù)字前可以加0來表示填齊時(shí)用“0”而非空格,如%08x表示輸出為8位前面用0填齊的16進(jìn)制數(shù)。
wsprintf是Win32 API中唯一一個(gè)參數(shù)數(shù)量不定的函數(shù),使用wsprintf函數(shù)的時(shí)候,參數(shù)的數(shù)量取決于格式化字符串中用%號指定的數(shù)量,變量列表的數(shù)目和格式化串中的%格式一定要一一對應(yīng)。這里szReceive中有兩個(gè)%號定義,那么后面就要額外跟兩個(gè)參數(shù):
invoke wsprintf, addr szBuffer, addr szReceive, lParam, lParam
這條語句將lParam的數(shù)值以及lParam的字符串按照szReceive格式化串定義的格式轉(zhuǎn)換,并將結(jié)果存放到szBuffer中,然后程序?qū)?/span>szBuffer中的內(nèi)容在一個(gè)消息框中顯示出來:
invoke MessageBox, hWnd, offset szBuffer, addr szCaptionMain, MB_OK
執(zhí)行程序?qū)懞昧耍F(xiàn)在寫一個(gè)發(fā)送程序,如下所示:
.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
在這個(gè)程序中首先用FindWindow函數(shù)找到接收窗口的窗口句柄,FindWindow函數(shù)的使用方法是:
invoke FindWindow, lpClassName, lpWindowName
.if eax
mov hWin, eax
.endif
兩個(gè)參數(shù)都指向字符串lpClassName指向需要尋找的窗口的窗口類,lpWindowName指向需要尋找窗口的窗口標(biāo)題,如果目標(biāo)窗口存在的話,函數(shù)的返回值是找到的窗口句柄,否則函數(shù)返回0。
用接收窗口的窗口類當(dāng)做參數(shù)尋找窗口,如果沒有找到則顯示“Receive Message Window not found”,找到的話則把“Text send to other windows”字符串的地址當(dāng)做WM_SETTEXT消息的參數(shù)用SendMessage發(fā)送給接收窗口。
好!,現(xiàn)在發(fā)送開始,首先執(zhí)行Receive.exe,窗口出來了,然后執(zhí)行Send.exe,屏幕上出現(xiàn)一個(gè)對話框:Press OK to start SendMessage, param:00402072,表示在Send程序中字符串的地址是00402072h,現(xiàn)在單擊“確定”按鈕執(zhí)行SendMessage函數(shù),單擊后對話框消失,但接收程序顯示出了一個(gè)對話框,內(nèi)容為:
Receive WM_SETTEXT message
param: 0012fflc
text: “Text send to other windows”
可見字符串是正確地傳了過來,但地址卻不是發(fā)送程序的00402072h,這是為何?
答案是Window做“紅娘”做到底,它拷貝了WM_SETTEXT消息的lParam參數(shù)指向的字符串,并在接收程序的地址空間中開了一塊內(nèi)存放上這個(gè)字符串,然后把新的地址值當(dāng)做lParam傳給接收程序,畢竟在WM_SETTEXT消息中,lParam的數(shù)值是多少并不重要,重要的是它指向的字符串是否正確。
最后,單擊接收程序中的“確定”按鈕,發(fā)送程序馬上彈出一個(gè)消息框并顯示:SendMessage returned,這是SendMessage函數(shù)告訴我們:我回來了!
其實(shí)Windows在處理SendMessage和PostMessage的時(shí)候要檢查消息的類型,并對不同的消息做不同的處理,當(dāng)消息的參數(shù)是一個(gè)指針的時(shí)候,Windows要把指針指向的內(nèi)容復(fù)制到緩沖區(qū),以便在兩個(gè)程序的地址空間中傳遞。
注意:在用戶自定義的消息中(WM_USER等)不要在消息參數(shù)中傳遞指針,這只會(huì)引發(fā)非法訪問內(nèi)存,因?yàn)?/span>Windows不知道用戶的意圖,它只會(huì)把lParam和wParam當(dāng)兩個(gè)普通的數(shù)值傳遞,而不會(huì)幫用戶把指針指向的內(nèi)容復(fù)制到一個(gè)緩沖區(qū)中。