上次介紹了如何在Delphi中使用發(fā)送消息的方式控制外部程序,一開(kāi)始我在自己的項(xiàng)目中也確實(shí)是這么做的,但是后來(lái)遇到了這么一個(gè)問(wèn)題:
我所調(diào)用的程序,會(huì)在執(zhí)行一段處理過(guò)程中,將結(jié)果顯示到一個(gè)ListView中,那么為了知道我發(fā)出的命令,到底被那個(gè)程序執(zhí)行后結(jié)果如何,我就必須監(jiān)視ListView中的內(nèi)容,察看最后出現(xiàn)的結(jié)果文字是什么,從而知道到底是成功還是失敗了。那么,我的想法是,不斷的查詢ListView中Items的個(gè)數(shù),并且當(dāng)個(gè)數(shù)大于0的時(shí)候,取出最后一條Item(就是最后加入的結(jié)果描述),然后取得其中的文字,通過(guò)判斷字符串,就可以知道結(jié)果了。
首先,ListView的窗口Handle我當(dāng)然是有了,然后取得ListView中的Item個(gè)數(shù),我發(fā)現(xiàn)有這么個(gè)函數(shù)可以使用:ListView_GetItemCount(),它在CommCtrl模塊中有定義,其實(shí)只是對(duì)SendMessage的一個(gè)封裝而已,同樣的,該模塊中還有另一個(gè)函數(shù):ListView_GetItemText(),使用它可以取得指定行處的Item文字。
那么只要在我的程序中使用這兩個(gè)函數(shù)就可以了咯?很抱歉,我用實(shí)際經(jīng)歷告訴你:這樣將會(huì)導(dǎo)致外部程序的崩潰!!
要說(shuō)明為什么,首先讓我們來(lái)看一下ListView_GetItemText()函數(shù)到底做了什么(另外還有ListView_GetItemTextA和ListView_GetItemTextW這兩個(gè)函數(shù),暫時(shí)不用去理它們):
function ListView_GetItemText(hwndLV: HWND; i, iSubItem: Integer;
  pszText: PChar; cchTextMax: Integer): Integer;
var
  Item: TLVItem;
begin
  Item.iSubItem := iSubItem;
  Item.cchTextMax := cchTextMax;
  Item.pszText := pszText;
  Result := SendMessage(hwndLV, LVM_GETITEMTEXT, i, Longint(@Item));
end;
相信你能看明白,它將一個(gè)字符串指針pszText放到item結(jié)構(gòu)中,再用SendMessage將該結(jié)構(gòu)地址傳給ListView窗口,在本地程序中這當(dāng)然不會(huì)有問(wèn)題,但是試想一下在外部exe中的情況,Item和pszText都是我的程序中的變量,外部exe有自己的獨(dú)立進(jìn)程空間,同樣的變量地址在它的進(jìn)程空間中指向的是完全不同的數(shù)據(jù),并且很有可能該數(shù)據(jù)正在被其它程序段使用,那么當(dāng)外部程序收到Message,并向該錯(cuò)誤的地址中寫入數(shù)據(jù)的時(shí)候,程序就這么崩潰了!
難道就沒(méi)有辦法解決這個(gè)問(wèn)題了嗎?當(dāng)然有!很簡(jiǎn)單,我只需要保證外部程序收到Message時(shí),將數(shù)據(jù)寫入到本地進(jìn)程空間中的地址中就可以了,那么這就意味著我必須在外部程序中開(kāi)辟一塊內(nèi)存,讓SendMessage來(lái)寫入,并且能夠從中將數(shù)據(jù)讀回來(lái),有什么辦法呢?在這里就要感謝Robert Kuster這個(gè)大師級(jí)人物了,他在以下這篇文章中詳細(xì)的介紹了多種將代碼注入外部程序的方法:
Three Ways to inject Your Code into Another PRocess
我選擇了第二種,即制作一個(gè)dll,由主程序?qū)⑦@個(gè)dll注入到外部程序的進(jìn)程空間中,以后就可以為所欲為了,哈哈!Robert Kuster的例子中,只需要用dll中的代碼取一個(gè)文本框中的密碼就可以了,而我的稍有不同,我需要不斷的監(jiān)視ListView中的狀態(tài),所以我使用了一個(gè)線程,dll被注入后,立即啟動(dòng)自己的線程,并進(jìn)入線程循環(huán),一直到外部程序關(guān)閉,線程會(huì)被自動(dòng)關(guān)閉(所以關(guān)閉的事,我就不管了)。
另外,既然有了線程,連控制外部程序的代碼都可以放在線程中,這么一來(lái)就變成這樣的一個(gè)流程了:
1。主程序再虛擬桌面上啟動(dòng)外部程序,并將自制dll注入外部程序的進(jìn)程
2。被注入的dll啟動(dòng)線程,線程的開(kāi)始,先找到所有需要使用的窗口的WindowHandle,這一點(diǎn)還是用FindWnidow方法。接著打開(kāi)一個(gè)共享內(nèi)存區(qū),用來(lái)和主程序通訊使用。
3。進(jìn)入dll的線程循環(huán),循環(huán)中使用一個(gè)event進(jìn)入等待狀態(tài)。
4。當(dāng)主程序中需要使用外部程序的功能時(shí),將控制命令寫入共享內(nèi)存中,然后觸發(fā)event。
5。被注入dll中的線程的event被觸發(fā),它從共享內(nèi)存中取得控制命令,然后使用SendMessage啟動(dòng)外部程序的功能,并進(jìn)入循環(huán),等待ListView中出現(xiàn)結(jié)束文字。
6。外部程序中的功能執(zhí)行結(jié)束后,dll中的線程再以event的方式通知主程序。
好了,這下真的是“完美”實(shí)現(xiàn)了!
由于商業(yè)上的原因,代碼我就不能公開(kāi)了,如果你還有什么疑問(wèn),倒是隨時(shí)歡迎來(lái)信討論:tonyki[at]citiz.net
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注