摘要:本文闡述了Windows環境下動態連結程式庫的概念和特點,對靜態調用和動態調用兩種調用方式作出了比較,並給出了Delphi中應用動態連結程式庫的執行個體。
一、動態連結程式庫的概念
動態連結程式庫(Dynamic Link Library,縮寫為DLL)是一個可以被其它應用程式共用的程式模組,其中封裝了一些可以被共用的常式和資源。動態連結程式庫檔案的副檔名一般是dll,也有可能是drv、sys和fon,它和可執行檔(exe)非常類似,區別在於DLL中雖然包含了可執行代碼卻不能單獨執行,而應由Windows應用程式直接或間接調用。
動態連結是相對於靜態連結而言的。所謂靜態連結是指把要調用的函數或者過程連結到可執行檔中,成為可執行檔的一部分。換句話說,函數和過程的代碼就在程式的exe檔案中,該檔案包含了運行時所需的全部代碼。當多個程式都調用相同函數時,記憶體中就會存在這個函數的多個拷貝,這樣就浪費了寶貴的記憶體資源。而動態連結所調用的函數代碼並沒有被拷貝到應用程式的可執行檔中去,而是僅僅在其中加入了所調用函數的描述資訊(往往是一些重定位資訊)。僅當應用程式被裝入記憶體開始運行時,在Windows的管理下,才在應用程式與相應的DLL之間建立連結關係。當要執行所調用DLL中的函數時,根據連結產生的重定位資訊,Windows才轉去執行DLL中相應的函數代碼。
一般情況下,如果一個應用程式使用了動態連結程式庫,Win32系統保證記憶體中只有DLL的一份複製品,這是通過記憶體對應檔實現的。DLL首先被調入Win32系統的全域堆棧,然後映射到調用這個DLL的進程地址空間。在Win32系統中,每個進程擁有自己的32位線性地址空間,如果一個DLL被多個進程調用,每個進程都會收到該DLL的一份映像。與16位Windows不同,在Win32中DLL可以看作是每個進程自己的代碼。
二、動態連結程式庫的優點
1. 共用代碼、資源和資料
使用DLL的主要目的就是為了共用代碼,DLL的代碼可以被所有的Windows應用程式共用。
2. 隱藏實現的細節
DLL中的常式可以被應用程式訪問,而應用程式並不知道這些常式的細節。
3. 拓展開發工具如Delphi的功能
由於DLL是與語言無關的,因此可以建立一個DLL,被C++、VB或任何支援動態連結程式庫的語言調用。這樣如果一種語言存在不足,就可以通過訪問另一種語言建立的DLL來彌補。
三、動態連結程式庫的實現方法
1. Load-time Dynamic Linking
這種用法的前提是在編譯之前已經明確知道要調用DLL中的哪幾個函數,編譯時間在目標檔案中只保留必要的連結資訊,而不含DLL函數的代碼;當程式執行時,利用連結資訊載入DLL函數代碼並在記憶體中將其連結入調用程式的執行空間中,其主要目的是便於代碼共用。
2. Run-time Dynamic Linking
這種方式是指在編譯之前並不知道將會調用哪些DLL函數,完全是在運行過程中根據需要決定應調用哪個函數,並用LoadLibrary和GetProcAddress動態獲得DLL函數的入口地址。
四、DLL的兩種調用方式在Delphi中的比較
編寫DLL的目的是為了輸出常式供其他程式調用,因此在DLL的工程檔案中要把輸出的常式用Exports關鍵字引出。在調用DLL的應用程式中,需要聲明用到的DLL中的方法,聲明格式要和DLL中的聲明一樣。訪問DLL中的常式有靜態調用和動態調用兩種方式。靜態調用方式就是在單元的Interface部分用External指示字列出要從DLL中引入的常式;動態調用方式就是通過調用Windows的API包括LoadLibrary函數、GetProcAddress函數以及FreeLibrary函數動態引入DLL中的常式。
靜態調用方式所需的代碼較動態調用方式所需的少,但存在著一些不足,一是如果要載入的DLL不存在或者DLL中沒有要引入的常式,這時候程式就自動終止運行;二是DLL一旦載入就一直駐留在應用程式的地址空間,即使DLL已不再需要了。動態調用方式就可解決以上問題,它在需要用到DLL的時候才通過LoadLibrary函數引入,用完後通過FreeLibrary函數從記憶體中卸載,而且通過調GetProcAddress函數可以指定不同的常式。最重要的是,如果指定的DLL出錯,至多是API調用失敗,不會導致程式終止。以下將通過具體的執行個體說明說明這調用方式的使用方法。
1. 靜態調用方式
樣本程式建立了一個DLL,其中僅包含一個求兩個整數的和的函數,在主程式中輸入兩個整數,通過調用該DLL,即可求出兩個整數的和,1所示。
該DLL的程式碼如下:
library AddNum; uses SysUtils, Classes;{$R *.res} function AddNumber(Num1,Num2:integer):integer;stdcall; //定義求和函數 begin result:=Num1+Num2; end; exports AddNumber; //引出求和函數 begin end. |
主程式在調用該DLL時,首先在interface部分聲明要調用的函數:
function AddNum(Num1,Num2:integer):integer;stdcall;external 'AddNum.dll' name 'AddNumber'; |
然後在按鈕控制項的事件中寫入如下代碼:
procedure TForm1.Button1Click(Sender: TObject); var Number1,Number2:integer; Sum:integer; begin Number1:=strtoint(Edit1.Text); Number2:=strtoint(Edit2.Text); Sum:=AddNum(Number1,Number2); //調用求和Function Compute結果 Edit3.Text:=inttostr(Sum); end; |
2.動態調用方式
這個樣本程式建立了一個顯示日期的DLL,其中包含一個表單,2所示。
程式中定義了一個ShowCalendar函數,返回在這個表單中設定的日期。函數定義如下:
function ShowCalendar(AHandle: THandle; ACaption: String): TDateTime; var DLLForm: TDLLForm; begin Application.Handle := AHandle; DLLForm := TDLLForm.Create(Application); //建立並顯示表單 try DLLForm.Caption := ACaption; DLLForm.ShowModal; //顯示方式為模式化 Result := DLLForm.calDLLCalendar.CalendarDate; //返回設定日期 finally DLLForm.Free; //用完後卸載該表單 end; end; |
在DLL的工程檔案中用exports ShowCalendar; 語句引出該函數。下面通過一個簡單的應用程式測試一下該DLL檔案。建立一個工程檔案,在表單中放置一個Label控制項和一個按鈕控制項,在按鈕控制項的OnClick事件中編寫如下代碼:
procedure TMainForm.Button1Click(Sender: TObject); var OneHandle : THandle; //定義一個控制代碼變數 begin OneHandle := LoadLibrary('Clendar.dll'); //動態載入DLL,並返回其控制代碼 try if OneHandle <> 0 then //如果載入成功則擷取ShowCalendar函數的地址 @ShowCalendar := GetProcAddress(OneHandle, 'ShowCalendar'); if not (@ShowCalendar = nil) then //如果找到該函數則在主表單的Label1中顯示DLL表單中設定的日期 Label1.Caption := DateToStr(ShowCalendar(Application.Handle, Caption)) else RaiseLastWin32Error; finally FreeLibrary(OneHandle); //調用完畢收回DLL佔用的資源 end; end; |
從以上程式中可以看到DLL的動態調用方式比靜態調用方式的優越之處。DLL常式在用到時才被調入,用完後就被卸載,大大減少了系統資源的佔用。在調用LoadLibrary函數時可以明確指定DLL的完整路徑,如果沒有指定路徑,運行時首先尋找應用程式載入的目錄,然後是Windows系統的System目錄和環境變數Path設定的路徑。
五、結束語
由於動態連結程式庫可以實現代碼和資源的共用,大大減少系統資源的佔用,因此在當今的應用程式開發中起著非常重要的作用。Delphi是現今流行的應用軟體開發工具,本文就如何在Delphi中使用動態連結程式庫給出了一定程度上的闡述。