直播中
但隨著
Windows這樣的多任務(wù)環(huán)境的出現(xiàn),函數(shù)庫的方法顯得過于累贅。如果為了完成屏幕輸出、消息處理、內(nèi)存管理、對(duì)話框等操作,每個(gè)程序都不得不擁有自己的函數(shù),那么Windows程序?qū)⒆兊梅浅}嫶蟆?/FONT>Windows的發(fā)展要求允許同時(shí)運(yùn)行的幾個(gè)程序共享一組函數(shù)的單一拷貝。動(dòng)態(tài)鏈接庫就是在這種情況下出現(xiàn)的。動(dòng)態(tài)鏈接庫不用重復(fù)編譯或鏈接,一旦裝入內(nèi)存,Dlls函數(shù)可以被系統(tǒng)中的任何正在運(yùn)行的應(yīng)用程序軟件所使用,而不必再將DLLs函數(shù)的另一拷貝裝入內(nèi)存?!?/P>10.1.1 動(dòng)態(tài)鏈接庫的工作原理
“動(dòng)態(tài)鏈接”這幾字指明了
DLLs是如何工作的。對(duì)于常規(guī)的函數(shù)庫,鏈接器從中拷貝它需要的所有庫函數(shù),并把確切的函數(shù)地址傳送給調(diào)用這些函數(shù)的程序。而對(duì)于DLLs,函數(shù)儲(chǔ)存在一個(gè)獨(dú)立的動(dòng)態(tài)鏈接庫文件中。在創(chuàng)建Windows程序時(shí),鏈接過程并不把DLLs文件鏈接到程序上。直到程序運(yùn)行并調(diào)用一個(gè)DLLs中的函數(shù)時(shí),該程序才要求這個(gè)函數(shù)的地址。此時(shí)Windows才在DLLs中尋找被調(diào)用函數(shù),并把它的地址傳送給調(diào)用程序。采用這種方法,DLLs達(dá)到了復(fù)用代碼的極限。動(dòng)態(tài)鏈接庫的另一個(gè)方便之處是對(duì)動(dòng)態(tài)鏈接庫中函數(shù)的修改可以自動(dòng)傳播到所有調(diào)用它的程序中,而不必對(duì)程序作任何改動(dòng)或處理。
DLLs不僅提供了函數(shù)重用的機(jī)制,而且提供了數(shù)據(jù)共享的機(jī)制。任何應(yīng)用程序都可以共享由裝入內(nèi)存的DLLs管理的內(nèi)存資源塊。只包含共享數(shù)據(jù)的DLLs稱為資源文件。如Windows的字體文件等?!?/P>
10.1.2 Windows系統(tǒng)的動(dòng)態(tài)鏈接庫
Windows本身就是由大量的動(dòng)態(tài)鏈接庫支持的。這包括Windows API函數(shù) ( KRNLx86.EXE,USER.EXE,GDI.EXE,…),各種驅(qū)動(dòng)程序文件,各種帶有.Fon和.Fot 擴(kuò)展名的字體資源文件等。Windows還提供了針對(duì)某一功能的專用DLLs,如進(jìn)行DDE編程的ddeml.dll,進(jìn)行程序安裝的ver.dll等。
雖然在編寫Windows程序時(shí)必然要涉及到DLLs,但利用Delphi ,用戶在大部分時(shí)候并不會(huì)注意到這一點(diǎn)。這一方面是因?yàn)?/FONT>Delphi提供了豐富的函數(shù)使用戶不必直接去使用Windows API;另一方面即使使用Windows API,由于Delphi把API函數(shù)和其它Windows DLLs函數(shù)重新組織到了幾個(gè)庫單元中,因而也不必使用特殊的調(diào)用格式。所以本章的重點(diǎn)放在編寫和調(diào)用用戶自定義的DLLs上。
使用傳統(tǒng)的Windows編程方法來創(chuàng)建和使用一個(gè)DLLs是一件很令人頭痛的事,正如傳統(tǒng)的Windows編程方法本身就令人生畏一樣。用戶需要對(duì)定義文件、工程文件進(jìn)行一系列的修改以適應(yīng)創(chuàng)建和使用DLLs的需要。Delphi的出現(xiàn),在這一方面,正如在其它許多方面所做的那樣,減輕了開發(fā)者的負(fù)擔(dān)。更令人興奮的是Delphi利用DLLs 實(shí)現(xiàn)了窗體的重用機(jī)制。用戶可以將自己設(shè)計(jì)好的窗體儲(chǔ)存在一個(gè)DLLs中,在需要的時(shí)候可隨時(shí)調(diào)用它?!?/P>
10.2 DLLs的編寫和調(diào)用
10.2.1 DLLs的編寫
在Delphi環(huán)境中,編寫一個(gè)DLLs同編寫一個(gè)一般的應(yīng)用程序并沒有太大的區(qū)別。事實(shí)上作為DLLs 主體的DLL函數(shù)的編寫,除了在內(nèi)存、資源的管理上有所不同外,并不需要其它特別的手段。真正的區(qū)別在工程文件上。
在絕大多數(shù)情況下,用戶幾乎意識(shí)不到工程文件的存在,因?yàn)樗话悴伙@示在屏幕上。如果想查看工程文件,則可以打開View菜單選擇Project Source項(xiàng),此時(shí)工程文件的代碼就會(huì)出現(xiàn)在屏幕的Code Editor(代碼編輯器)中。
一般工程文件的格式為:
program 工程標(biāo)題;
uses 子句;
程序體
而
DLLs工程文件的格式為:library 工程標(biāo)題;
uses 子句;
exprots 子句;
程序體
它們主要的區(qū)別有兩點(diǎn):
1.一般工程文件的頭標(biāo)用program關(guān)鍵字,而DLLs工程文件頭標(biāo)用library 關(guān)鍵字。不同的關(guān)鍵字通知編譯器生成不同的可執(zhí)行文件。用program關(guān)鍵字生成的是.exe文件,而用library關(guān)鍵字生成的是.dll文件;
2.假如DLLs要輸出供其它應(yīng)用程序使用的函數(shù)或過程,則必須將這些函數(shù)或過程列在exports子句中。而這些函數(shù)或過程本身必須用export編譯指令進(jìn)行編譯。
根據(jù)
DLLs完成的功能,我們把DLLs分為如下的三類:1.完成一般功能的DLLs;
2.用于數(shù)據(jù)交換的DLLs;
3.用于窗體重用的DLLs。
這一節(jié)我們只討論完成一般功能的DLLs,其它內(nèi)容將在后邊的兩節(jié)中討論。
10.2.1.1 編寫一般DLLs的步驟
編寫一般DLLs的步驟如下:
1.利用Delphi的應(yīng)用程序模板,建立一個(gè)DLLs程序框架。
對(duì)于Delphi 1.0的用戶,由于沒有DLLs模板,因此:
(1).建立一個(gè)一般的應(yīng)用程序,并打開工程文件;
(2).移去窗體和相應(yīng)的代碼單元;
(3).在工程文件中,把program改成library,移去Uses子句中的Forms,并添加適當(dāng)?shù)膸靻卧ㄒ话?/FONT>SysUtils、Classes是需要的),刪去begin...end之間的所有代碼。
2.以適當(dāng)?shù)奈募3治募?,此時(shí)library后跟的庫名自動(dòng)修改;
3.輸入過程、函數(shù)代碼。如果過程、函數(shù)準(zhǔn)備供其它應(yīng)用程序調(diào)用,則在過程、函數(shù)頭后加上export 編譯指示;
4.建立exports子句,包含供其它應(yīng)用程序調(diào)用的函數(shù)和過程名??梢岳脴?biāo)準(zhǔn)指示 name 、Index、resident以方便和加速過程/函數(shù)的調(diào)用;
5.輸入庫初始化代碼。這一步是可選的;
6.編譯程序,生成動(dòng)態(tài)鏈接庫文件?!?/P>
10.2.1.2 動(dòng)態(tài)鏈接庫中的標(biāo)準(zhǔn)指示
在動(dòng)態(tài)鏈接庫的輸出部分,用到了三個(gè)標(biāo)準(zhǔn)指示:name、Index、resident。
1.name
name后面接一個(gè)字符串常量,作為該過程或函數(shù)的輸出名。如:
exports
InStr name MyInstr;
其它應(yīng)用程序?qū)⒂眯旅?/FONT>(MyInstr)調(diào)用該過程或函數(shù)。如果仍利用原來的名字(InStr),則在程序執(zhí)行到引用點(diǎn)時(shí)會(huì)引發(fā)一個(gè)系統(tǒng)錯(cuò)誤。
2.Index
Index指示為過程或函數(shù)分配一個(gè)順序號(hào)。如果不使用Index指示,則由編譯器按順序進(jìn)行分配。
Index后所接數(shù)字的范圍為1...32767。使用Index可以加速調(diào)用過程。
3.resident
使用
resident,則當(dāng)DLLs裝入時(shí)特定的輸出信息始終保持在內(nèi)存中。這樣當(dāng)其它應(yīng)用程序調(diào)用該過程時(shí),可以比利用名字掃描DLL入口降低時(shí)間開銷。對(duì)于那些其它應(yīng)用程序常常要調(diào)用的過程或函數(shù),使用
resident指示是合適的。例如:exports
InStr name MyInStr resident;
10.2.1.3 DLLs中的變量和段
一個(gè)DLLs擁有自己的數(shù)據(jù)段(DS),因而它聲明的任何變量都為自己所私有。調(diào)用它的模塊不能直接使用它定義的變量。要使用必須通過過程或函數(shù)界面才能完成。而對(duì)DLLs來說,它永遠(yuǎn)都沒有機(jī)會(huì)使用調(diào)用它的模塊中聲明的變量。
一個(gè)DLLs沒有自己的堆棧段(SS),它使用調(diào)用它的應(yīng)用程序的堆棧。因此在DLL中的過程、函數(shù)絕對(duì)不要假定DS = SS。一些語言在小模式編譯下有這種假設(shè),但使用Delphi可以避免這種情況。Delphi絕不會(huì)產(chǎn)生假定DS = SS的代碼,Delphi的任何運(yùn)行時(shí)間庫過程/函數(shù)也都不作這種假定。需注意的是如果讀者想嵌入?yún)R編語言代碼,絕不要使SS和DS登錄同一個(gè)值?!?/P>
10.2.1.4 DLLs中的運(yùn)行時(shí)間錯(cuò)和處理
由于DLLs無法控制應(yīng)用程序的運(yùn)行,導(dǎo)致很難進(jìn)行異常處理,因此編寫DLLs時(shí)要十分小心,以確保被調(diào)用時(shí)能正常執(zhí)行 。當(dāng)DLLs中發(fā)生一個(gè)運(yùn)行時(shí)間錯(cuò)時(shí),相應(yīng)DLLs并不一定從內(nèi)存中移去(因?yàn)榇藭r(shí)其它應(yīng)用程序可能正在用它),而調(diào)用DLLs的程序異常中止。這樣造成的問題是當(dāng)DLLs已被修改,重新進(jìn)行調(diào)用時(shí),內(nèi)存中保留的仍然可能是以前的版本,修改后的程序并沒有得到驗(yàn)證。對(duì)于這個(gè)問題,有以下兩種解決方法:
1.在程序的異常處理部分顯式將DLL卸出內(nèi)存;
2.完全退出Windows,而后重新啟動(dòng),運(yùn)行相應(yīng)的程序。
同一般的應(yīng)用程序相比,DLL中運(yùn)行時(shí)間錯(cuò)的處理是很困難的,而造成的后果也更為嚴(yán)重。因此要求程序設(shè)計(jì)者在編寫代碼時(shí)要有充分、周到的考慮。
10.2.1.5 庫初始化代碼的編寫
傳統(tǒng)Windows中動(dòng)態(tài)鏈接庫的編寫,需要兩個(gè)標(biāo)準(zhǔn)函數(shù):LibMain和WEP,用于啟動(dòng)和關(guān)閉DLL。在LibMain中,可以執(zhí)行開鎖DLL數(shù)據(jù)段、分配內(nèi)存、初始化變量等初始化工作;而WEP在從內(nèi)存中移去DLLs前被調(diào)用,一般用于進(jìn)行必要的清理工作,如釋放內(nèi)存等。Delphi用自己特有的方式實(shí)現(xiàn)了這兩個(gè)標(biāo)準(zhǔn)函數(shù)的功能。這就是在工程文件中的begin...end部分添加初始化代碼。和傳統(tǒng)Windows編程方法相比,它的主要特色是:
1.初始化代碼是可選的。一些必要的工作(如開鎖數(shù)據(jù)段)可以由系統(tǒng)自動(dòng)完成。所以大部分情況下用戶不會(huì)涉及到;
2.可以設(shè)置多個(gè)退出過程,退出時(shí)按順序依次被調(diào)用;
3.LibMain和WEP對(duì)用戶透明,由系統(tǒng)自動(dòng)調(diào)用。
初始化代碼完成的主要工作是:
1.初始化變量、分配全局內(nèi)存塊、登錄窗口對(duì)象等初始化工作。在(10.3.2)節(jié)“利用DLLs實(shí)現(xiàn)應(yīng)用程序間的數(shù)據(jù)傳輸”中,用于數(shù)據(jù)共享的全局內(nèi)存塊就是在初始化代碼中分配的。
2.設(shè)置DLLs退出時(shí)的執(zhí)行過程。Delphi有一個(gè)預(yù)定義變量ExitProc用于指向退出過程的地址。用戶可以把自己的過程名賦給ExitProc。系統(tǒng)自動(dòng)調(diào)用WEP函數(shù),把ExitProc指向的地址依次賦給WEP執(zhí)行,直到ExitProc為nil。
下邊的一段程序包含一個(gè)退出過程和一段初始化代碼,用來說明如何正確設(shè)置退出過程?!?/P>
library Test;
{$S-}
uses WinTypes, WinProcs;
var
SaveExit: Pointer;
procedure LibExit; far;
begin
if ExitCode = wep_System_Exit then
begin
{ 系統(tǒng)關(guān)閉時(shí)的相應(yīng)處理 }
end
else
begin
{ DLL卸出時(shí)的相應(yīng)處理 }
end;
ExitProc := SaveExit; { 恢復(fù)原來的退出過程指針 }
end;
begin
{DLL的初始化工作 }
SaveExit := ExitProc; { 保存原來的退出過程指針 }
ExitProc := @LibExit; { 安裝新的退出過程 }
end.
在初始化代碼中,首先把原來的退出過程指針保存到一個(gè)變量中,而后再把新的退出過程地址賦給
ExitProc。而在自定義退出過程LibExit結(jié)束時(shí)再把ExitProc的值恢復(fù)。由于ExitProc是一個(gè)系統(tǒng)全局變量,所以在結(jié)束時(shí)恢復(fù)原來的退出過程是必要的。退出過程
LibExit中使用了一個(gè)系統(tǒng)定義變量ExitCode,用于標(biāo)志退出時(shí)的狀態(tài)。 ExitCode的取值與意義如下:表10.1 ExitCode的取值與意義
━━━━━━━━━━━━━━━━━━━━━
取 值 意 義
—————————————————————
WEP_System_Exit Windows關(guān)閉
WEP_Free_DLLx DLLs被卸出
━━━━━━━━━━━━━━━━━━━━━
退出過程編譯時(shí)必須關(guān)閉stack_checking,因而需設(shè)置編譯指示 {$S-} ?!?/P>
10.2.1.6 編寫一般DLLs的應(yīng)用舉例
在下面的程序中我們把一個(gè)字符串操作的函數(shù)儲(chǔ)存到一個(gè)DLLs中,以便需要的時(shí)候調(diào)用它。應(yīng)該注意的一點(diǎn)是:為了保證這個(gè)函數(shù)可以被其它語言編寫的程序所調(diào)用,作為參數(shù)傳遞的字符串應(yīng)該是無結(jié)束符的字符數(shù)組類型(即PChar類型),而不是Object Pascal的帶結(jié)束符的Srting類型。程序清單如下:
library Example;
uses
SysUtils,
Classes;
{返回字符在字符串中的位置}
function InStr(SourceStr: PChar;Ch: Char): Integer; export;
var
Len,i: Integer;
begin
Len := strlen(SourceStr);
for i := 0 to Len-1 do
if SourceStr[i] = ch then
begin
Result := i;
Exit;
end;
Result := -1;
end;
exports
Instr Index 1 name 'MyInStr' resident;
begin
end.
10.2.2 調(diào)用DLLs
有兩種方法可用于調(diào)用一個(gè)儲(chǔ)存在
DLLs中的過程。1.靜態(tài)調(diào)用或顯示裝載
使用一個(gè)外部聲明子句,使
DLLs在應(yīng)用程序開始執(zhí)行前即被裝入。例如:function Instr(SourceStr : PChar;Check : Char); Integer; far; external 'UseStr';
使用這種方法,程序無法在運(yùn)行時(shí)間里決定
DLLs的調(diào)用。假如一個(gè)特定的DLLs在運(yùn)行時(shí)無法使用,則應(yīng)用程序?qū)o法執(zhí)行。2.動(dòng)態(tài)調(diào)用或隱式裝載
使用
Windows API函數(shù)LoadLibray和GetProcAddress可以實(shí)現(xiàn)在運(yùn)行時(shí)間里動(dòng)態(tài)裝載DLLs并調(diào)用其中的過程。若程序只在其中的一部分調(diào)用
DLLs的過程,或者程序使用哪個(gè)DLLs, 調(diào)用其中的哪個(gè)過程需要根據(jù)程序運(yùn)行的實(shí)際狀態(tài)來判斷,那么使用動(dòng)態(tài)調(diào)用就是一個(gè)很好的選擇。使用動(dòng)態(tài)調(diào)用,即使裝載一個(gè)
DLLs失敗了,程序仍能繼續(xù)運(yùn)行。10.2.3 靜態(tài)調(diào)用
在靜態(tài)調(diào)用一個(gè)DLLs中的過程或函數(shù)時(shí),external指示增加到過程或函數(shù)的聲明語句中。被調(diào)用的過程或函數(shù)必須采用遠(yuǎn)調(diào)用模式。這可以使用far過程指示或一個(gè){$F +}編譯指示。
Delphi全部支持傳統(tǒng)Windows動(dòng)態(tài)鏈接庫編程中的三種調(diào)用方式,它們是:
● 通過過程/函數(shù)名
● 通過過程/函數(shù)的別名
● 通過過程/函數(shù)的順序號(hào)
通過過程或函數(shù)的別名調(diào)用,給用戶編程提供了靈活性,而通過順序號(hào)(Index)調(diào)用可以提高相應(yīng)DLL的裝載速度。
10.2.4 動(dòng)態(tài)調(diào)用
10.2.4.1 動(dòng)態(tài)調(diào)用中的API函數(shù)
動(dòng)態(tài)調(diào)用中使用的Windows API函數(shù)主要有三個(gè),即:Loadlibrary,GetProcAddress和Freelibrary。
1.Loadlibrary: 把指定庫模塊裝入內(nèi)存
語法為:
function Loadlibrary(LibFileName: PChar): THandle;
LibFileName指定了要裝載DLLs的文件名,如果LibFileName沒有包含一個(gè)路徑,則Windows按下述順序進(jìn)行查找:
(1)當(dāng)前目錄;
(2)Windows目錄(包含win.com的目錄)。函數(shù)GetWindowDirectory返回這一目錄的路徑;
(3)Windows系統(tǒng)目錄(包含系統(tǒng)文件如gdi.exe的目錄)。函數(shù)GetSystemDirectory返回這一目錄的路徑;
(4)包含當(dāng)前任務(wù)可執(zhí)行文件的目錄。利用函數(shù)GetModuleFileName可以返回這一目錄的路徑;
(5)列在PATH環(huán)境變量中的目錄;
(6)網(wǎng)絡(luò)的映象目錄列表。
如果函數(shù)執(zhí)行成功,則返回裝載庫模塊的實(shí)例句柄。否則,返回一個(gè)小于HINSTANCE_ERROR的錯(cuò)誤代碼。錯(cuò)誤代碼的意義如下表:
表10.2 Loadlibrary返回錯(cuò)誤代碼的意義
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
錯(cuò)誤代碼 意 義
——————————————————————————————————————
0 系統(tǒng)內(nèi)存不夠,可執(zhí)行文件被破壞或調(diào)用非法
2 文件沒有被發(fā)現(xiàn)
3 路徑?jīng)]有被發(fā)現(xiàn)
5 企圖動(dòng)態(tài)鏈接一個(gè)任務(wù)或者有一個(gè)共享或網(wǎng)絡(luò)保護(hù)錯(cuò)
6 庫需要為每個(gè)任務(wù)建立分離的數(shù)據(jù)段
8 沒有足夠的內(nèi)存啟動(dòng)應(yīng)用程序
10 Windows版本不正確
11 可執(zhí)行文件非法?;蛘卟皇?/FONT>Windows應(yīng)用程序,或者在.EXE映
像中有錯(cuò)誤
12 應(yīng)用程序?yàn)橐粋€(gè)不同的操作系統(tǒng)設(shè)計(jì)(如OS/2程序)
13 應(yīng)用程序?yàn)?/FONT>MS DOS4.0設(shè)計(jì)
14 可執(zhí)行文件的類型不知道
15 試圖裝載一個(gè)實(shí)模式應(yīng)用程序(為早期Windows版本設(shè)計(jì))
16 試圖裝載包含可寫的多個(gè)數(shù)據(jù)段的可執(zhí)行文件的第二個(gè)實(shí)例
19 試圖裝載一個(gè)壓縮的可執(zhí)行文件。文件必須被解壓后才能被裝裁
20 動(dòng)態(tài)鏈接庫文件非法
21 應(yīng)用程序需要32位擴(kuò)展
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
假如在應(yīng)用程序用
Loadlibrary調(diào)用某一模塊前,其它應(yīng)用程序已把該模塊裝入內(nèi)存,則Loadlibrary并不會(huì)裝載該模塊的另一實(shí)例,而是使該模塊的“引用計(jì)數(shù)”加1。2.GetProcAddress:撿取給定模塊中函數(shù)的地址
語法為:
function GetProcAddress(Module: THandle; ProcName: PChar): TFarProc;
Module包含被調(diào)用的函數(shù)庫模塊的句柄,這個(gè)值由Loadlibrary返回。如果把Module設(shè)置為nil,則表示要引用當(dāng)前模塊。
ProcName是指向含有函數(shù)名的以nil結(jié)尾的字符串的指針,或者也可以是函數(shù)的次序值。如果ProcName參數(shù)是次序值,則如果該次序值的函數(shù)在模塊中并不存在時(shí),GetProcAddress仍返回一個(gè)非nil的值。這將引起混亂。因此大部分情況下用函數(shù)名是一種更好的選擇。如果用函數(shù)名,則函數(shù)名的拼寫必須與動(dòng)態(tài)鏈接庫文件EXPORTS節(jié)中的對(duì)應(yīng)拼寫相一致。
如果GetProcAddress執(zhí)行成功,則返回模塊中函數(shù)入口處的地址,否則返回nil。
3.Freelibrary:從內(nèi)存中移出庫模塊
語法為:
procedure Freelibrary(Module : THandle);
Module為庫模塊的句柄。這個(gè)值由Loadlibrary返回。
由于庫模塊在內(nèi)存中只裝載一次,因而調(diào)用Freelibrary首先使庫模塊的引用計(jì)數(shù)減一。如果引用計(jì)數(shù)減為0,則卸出該模塊。
每調(diào)用一次Loadlibrary就應(yīng)調(diào)用一次FreeLibray,以保證不會(huì)有多余的庫模塊在應(yīng)用程序結(jié)束后仍留在內(nèi)存中?!?/P>
10.2.4.2 動(dòng)態(tài)調(diào)用舉例
對(duì)于動(dòng)態(tài)調(diào)用,我們舉了如下的一個(gè)簡單例子。系統(tǒng)一共包含兩個(gè)編輯框。在第一個(gè)編輯框中輸入一個(gè)字符串,而后在第二個(gè)編輯框中輸入字符。如果該字符包含在第一個(gè)編輯框的字符串中,則標(biāo)簽框顯示信息:“位于第n位?!保駝t顯示信息:“不包含這個(gè)字符?!?。如圖是程序的運(yùn)行界面。
輸入檢查功能的實(shí)現(xiàn)在Edit2的OnKeyPress事件處理過程中,程序清單如下。
procedure TForm1.Edit2KeyPress(Sender: TObject; var Key: Char);
var
order: Integer;
txt: PChar;
PFunc: TFarProc;
Moudle: THandle;
begin
Moudle := Loadlibrary('c:\dlls\example.dll');
if Moudle > 32 then
begin
Edit2.text := '';
Pfunc := GetProcAddress(Moudle,'Instr');
txt := StrAlloc(80);
txt := StrPCopy(txt,Edit1.text);
Order := TInstr(PFunc)(txt,Key);
if Order = -1 then
Label1.Caption := '不包含這個(gè)字符 '
else
Label1.Caption := '位于第'+IntToStr(Order+1)+'位';
end;
Freelibrary(Moudle);
end;
在利用GetProcAddess返回的函數(shù)指針時(shí),必須進(jìn)行強(qiáng)制類型轉(zhuǎn)換:
Order := TInstr(PFunc)(text,Key);
TInStr是一個(gè)定義好了的函數(shù)類型:
type
TInStr = function(Source: PChar;Check: Char): Integer;
10.3 利用DLLs實(shí)現(xiàn)數(shù)據(jù)傳輸
10.3.1 DLLs中的全局內(nèi)存
Windows規(guī)定:DLLs并不擁有它打開的任何文件或它分配的任何全局內(nèi)存塊。這些對(duì)象由直接或間接調(diào)用DLLs的應(yīng)用程序擁有。這樣,當(dāng)應(yīng)用程序中止時(shí),它擁有的打開的文件自動(dòng)關(guān)閉,它擁有的全局內(nèi)存塊自動(dòng)釋放。這就意味著保存在DLLs全局變量中的文件和全局內(nèi)存塊變量在DLLs沒有被通知的情況下就變?yōu)榉欠?。這將給其它使用該DLLs的應(yīng)用程序造成困難。
為了避免出現(xiàn)這種情況,文件和全局內(nèi)存塊句柄不應(yīng)作為DLLs的全局變量,而是作為DLLs中過程或函數(shù)的參數(shù)傳遞給DLLs使用。調(diào)用DLLs的應(yīng)用程序應(yīng)該負(fù)責(zé)對(duì)它們的維護(hù)。
但在特定情況下,DLLs也可以擁有自己的全局內(nèi)存塊。這些內(nèi)存塊必須用gmem_DDEShare屬性進(jìn)行分配。這樣的內(nèi)存塊直到被DLLs顯示釋放或DLLs退出時(shí)都保持有效。
由DLLs管理的全局內(nèi)存塊是應(yīng)用程序間進(jìn)行數(shù)據(jù)傳輸?shù)挠忠煌緩?,下面我們將專門討論這一問題。
10.3.2 利用DLLs實(shí)現(xiàn)應(yīng)用程序間的數(shù)據(jù)傳輸
利用DLLs實(shí)現(xiàn)應(yīng)用程序間的數(shù)據(jù)傳輸?shù)牟襟E為:
1. 編寫一個(gè)DLLs程序,其中擁有一個(gè)用gmem_DDEShare屬性分配的全局內(nèi)存塊;
2. 服務(wù)器程序調(diào)用DLLs,向全局內(nèi)存塊寫入數(shù)據(jù);
3. 客戶程序調(diào)用DLLs,從全局內(nèi)存塊讀取數(shù)據(jù)。
10.3.2.1 用于實(shí)現(xiàn)數(shù)據(jù)傳輸?shù)腄LLs的編寫
用于實(shí)現(xiàn)數(shù)據(jù)傳輸?shù)腄LLs與一般DLLs的編寫基本相同,其中特別的地方是:
1. 定義一個(gè)全局變量句柄:
var
hMem: THandle;
2. 定義一個(gè)過程,返回該全局變量的句柄。該過程要包含在exports子句中。如:
function GetGlobalMem: THandle; export;
begin
Result := hMem;
end;
3. 在初始化代碼中分配全局內(nèi)存塊:
程序清單如下:
begin
hMem := GlobalAlloc(gmem_MOVEABLE and gmem_DDEShare,num);
if hMem = 0 then
MessageDlg('Could not allocate memory',mtWarning,[mbOK],0);
end.
num是一個(gè)預(yù)定義的常數(shù)。
Windows API函數(shù)GlobalAlloc用于從全局內(nèi)存堆中分配一塊內(nèi)存,并返回該內(nèi)存塊的句柄。該函數(shù)包括兩個(gè)參數(shù),第一個(gè)參數(shù)用于設(shè)置內(nèi)存塊的分配標(biāo)志。可以使用的分配標(biāo)志如下表所示。
表10.3 全局內(nèi)存塊的分配標(biāo)志
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
標(biāo) 志 意 義
—————————————————————————————————
gmem_DDEShare 分配可由應(yīng)用程序共享的內(nèi)存
gmem_Discardable 分配可拋棄的內(nèi)存(只與gmem_Moveable連用)
gmem_Fixed 分配固定內(nèi)存
gmem_Moveable 分配可移動(dòng)的內(nèi)存
gmem_Nocompact 該全局堆中的內(nèi)存不能被壓縮或拋棄
gmem_Nodiscard 該全局堆中的內(nèi)存不能被拋棄
gmem_NOT_Banked 分配不能被分段的內(nèi)存
gmem_Notify 通知功能。當(dāng)該內(nèi)存被拋棄時(shí)調(diào)用GlobalNotify函數(shù)
gmem_Zeroinit 將所分配內(nèi)存塊的內(nèi)容初始化為零
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
有兩個(gè)預(yù)定義的常用組合是:
GHND = gmem_Moveable and gmem_Zeroinit
GPTK = gmem_Fixed and gmem_Zeroinit
第二個(gè)參數(shù)用于設(shè)置欲分配的字節(jié)數(shù)。分配的字節(jié)數(shù)必須是32的倍數(shù),因而實(shí)際分配的字節(jié)數(shù)可能比所設(shè)置的要大。
由于用gmem_DDEShare分配的內(nèi)存在分配內(nèi)存的模塊終止時(shí)自動(dòng)拋棄,因而不必調(diào)用GlobalFree顯式釋放內(nèi)存。