摘要:本文闡述了 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; |
主程序在調用該DLL 時,首先在interface 部分聲明要調用的函數:
| function AddNum(Num1,Num2:integer):integer;stdcall;external 'AddNum.dll' |
然后在按鈕控件的事件中寫入如下代碼:
| procedure TForm1.Button1Click(Sender: TObject); |
2 .動態調用方式
這個示例程序創建了一個顯示日期的DLL ,其中包含一個窗體,如圖2 所示。
程序中定義了一個ShowCalendar 函數,返回在這個窗體中設定的日期。函數定義如下:
| function ShowCalendar(AHandle: THandle; ACaption: String): TDateTime; |
在DLL 的工程文件中用exports ShowCalendar; 語句引出該函數。下面通過一個簡單的應用程序測試一下該DLL 文件。新建一個工程文件,在窗體中放置一個Label 控件和一個按鈕控件,在按鈕控件的OnClick 事件中編寫如下代碼:
| procedure TMainForm.Button1Click(Sender: TObject); |
從以上程序中可以看到DLL 的動態調用方式比靜態調用方式的優越之處。DLL 例程在用到時才被調入,用完后就被卸載,大大減少了系統資源的占用。在調用 LoadLibrary 函數時可以明確指定DLL 的完整路徑,如果沒有指定路徑,運行時首先查找應用程序載入的目錄,然后是Windows 系統的 System 目錄和環境變量Path 設定的路徑。
五、結束語
由於動態鏈接庫可以實現代碼和資源的共享,大大減少系統資源的占用,因此在當今的應用程序開發中起着非常重要的作用。Delphi 是現今流行的應用軟件開發工具,本文就如何在Delphi 中使用動態鏈接庫給出了一定程度上的闡述。
