維基百科-動態鏈接庫

動態鏈接庫Dynamic Link Library或者Dynamic-link library,縮寫爲DLL),是微軟公司微軟視窗操作系統中實現共享函數庫概念的一種實作方式。這些庫函數的擴展名.DLL.OCX(包含ActiveX控制的庫)或者.DRV(舊式的系統驅動程序)。

所謂動態鏈接,就是把一些經常會共用的代碼(靜態鏈接的OBJ程序庫)製作成DLL文件,當可執行文件調用到DLL文件內的函數時,windows操作系統纔會把DLL文件加載存儲器內,DLL文件本身的結構就是可執行文件,當程序需求函數才進行鏈接。通過動態鏈接方式,存儲器浪費的情形將可大幅降低。

DLL的文檔格式與視窗EXE文檔一樣——也就是說,等同於32位視窗的可移植執行文檔(PE)和16位視窗的New Executable(NE)。作爲EXE格式,DLL可以包括源代碼數據資源的多種組合。

在更廣泛的意義上說,任何同樣文檔格式計算機文件都可以稱作資源DLL。這樣的DLL的例子有擴展名爲ICL圖標、擴展名爲FONFOT字體文檔。

背景[編輯]

DLL的最初目的是節約應用程序所需的磁盤和內存空間。在一個傳統的非共享庫中,一部分代碼簡單地附加到調用的程序上。如果兩個程序調用同一個子程序,就會出現兩份那段代碼。相反,許多應用共享的代碼能夠切分到一個DLL中,在硬盤上存爲一個文檔,在內存中使用一個實例(instance)。DLL的廣泛應用使得早期的視窗能夠在緊張的內存條件下運行。

DLL提供瞭如模塊化這樣的共享庫的普通好處。模塊化允許僅僅更改幾個應用程序共享使用的一個DLL中的代碼和數據而不需要更改應用程序自身。這種模塊化的基本形式允許如Microsoft OfficeMicrosoft Visual Studio、甚至Microsoft Windows自身這樣大的應用程序使用較爲緊湊的補丁和服務包

模塊化的另外一個好處是插件的通用接口使用。單個的接口允許舊的模塊與新的模塊一樣能夠與以前的應用程序運行時無縫地集成到一起,而不需要對應用程序本身作任何更改。這種動態擴展的思想在ActiveX中發揮到了極致。

儘管有這麼多的優點,使用DLL也有一個缺點:DLL地獄,也就是幾個應用程序在使用同一個共享DLL庫發生版本衝突。這樣的衝突可以通過將不同版本的問題DLL放到應用程序所在的文件夾而不是放到系統文件夾來解決;但是,這樣將抵消共享DLL節約的空間。目前,Microsoft .NET將解決DLL hell問題當作自己的目標,它允許同一個共享庫的不同版本並列共存。由於現代的計算機有足夠的磁盤空間和內存,這也可以作爲一個合理的實現方法。

特徵[編輯]

內存管理[編輯]

Win32中,DLL文檔按照片段(sections)進行組織。每個片段有它自己的屬性,如可寫或是隻讀、可執行(代碼)或者不可執行(數據)等等。

DLL代碼段通常被使用這個DLL的進程所共享;也就是說它們在物理內存中佔據一個地方,並且不會出現在頁面文檔中。如果代碼段所佔據的物理內存被收回,它的內容就會被放棄,後面如果需要的話就直接從DLL文檔重新加載。

與代碼段不同,DLL的數據段通常是私有的;也就是說,每個使用DLL的進程都有自己的DLL數據副本。作爲選擇,數據段可以設置爲共享,允許通過這個共享內存區域進行進程間通信。但是,因爲用戶權限不能應用到這個共享DLL內存,這將產生一個安全漏洞;也就是一個進程能夠破壞共享數據,這將導致其它的共享進程異常。例如,一個使用訪客賬號的進程將可能通過這種方式破壞其它運行在特權賬號的進程。這是在DLL中避免使用共享片段的一個重要原因。

當DLL被如UPX這樣一個可執行的packer壓縮時,它的所有代碼段都標記爲可以讀寫並且是非共享的。可以讀寫的代碼段,類似於私有數據段,是每個進程私有的並且被頁面文檔備份。這樣,壓縮DLL將同時增加內存和磁盤空間消耗,所以共享DLL應當避免使用壓縮DLL。

符號解析和綁定[編輯]

DLL輸出的每個函數都由一個數字序號唯一標識,也可以由可選的名字標識。同樣,DLL引入的函數也可以由序號或者名字標識。對於內部函數來說,只輸出序號的情形很常見。對於大多數視窗API函數來說名字是不同視窗版本之間保留不變的;序號有可能會發生變化。這樣,我們不能根據序號引用視窗API函數。

按照序號引用函數並不一定比按照名字引用函數性能更好:DLL輸出表是按照名字排列的,所以對半查找可以用來在在這個表中根據名字查找這個函數。另外一方面,只有線性查找纔可以用於根據序號查找函數。

將一個可執行文件綁定到一個特定版本的DLL也是可能的,這也就是說,可以在編譯時解析輸入函數(imported functions)的地址。對於綁定的輸入函數,連結工具保存了輸入函數綁定的DLL的時間戳和校驗和。在運行時Windows檢查是否正在使用同樣版本的庫,如果是的話,Windows將繞過處理輸入函數;否則如果庫與綁定的庫不同,Windows將按照正常的方式處理輸入函數。

綁定的可執行文件如果運行在與它們編譯所用的環境一樣,函數調用將會較快,如果是在一個不同的環境它們就等同於正常的調用,所以綁定輸入函數沒有任何的缺點。例如,所有的標準Windows應用程序都綁定到它們各自的Windows發佈版本的系統DLL。將一個應用程序輸入函數綁定到它的目的環境的好機會是在應用程序安裝的過程。

運行時顯式鏈接[編輯]

對每個DLL來說,Windows存儲了一個全局計數器,每多一個進程使用便多額外一個。LoadLibrary與FreeLibrary指令影響每一個進程內含的計數器;動態鏈接則不影響。因此藉由調用FreeLibrary多次,從存儲器反加載一DLL是很重要的。一個進程可以從它自己的VAS註銷此計數器。

DLL文檔能夠在運行時使用LoadLibrary(或者LoadLibraryEx)API函數進行顯式調用,這個的過程微軟簡單地稱爲運行時動態調用。API函數GetProcAddress根據查找輸出名稱符號、FreeLibrary卸載DLL。這些函數類似於POSIX標準API中的dlopendlsym、和dlclose

注意微軟簡單稱爲運行時動態鏈接的運行時隱式鏈接,如果不能找到鏈接的DLL文檔,Windows將提示一個錯誤消息並且調用應用程序失敗。應用程序開發人員不能通過編譯鏈接來處理這種缺少DLL文檔的隱式鏈接問題。另外一方面,對於顯式鏈接,開發人員有機會提供一個完善的出錯處理機制。

運行時顯式鏈接的過程在所有語言中都是相同的,因爲它依賴於Windows API而不是語言結構。

編譯器和語言考慮[編輯]

Delphi[編輯]

在源文件的開頭使用關鍵詞library而不是program,在文檔的末尾輸出函數使用exports排列。

Delphi不需要LIB文檔以從DLL中輸入函數。爲了鏈接一個DLL,在函數聲明中使用關鍵詞external

微軟Visual Basic[編輯]

在Visual Basic(VB)中只支持運行時鏈接;但是除了使用LoadLibraryGetProcAddress這兩個API函數之外,允許使用輸入函數的聲明來引入DLL函數,如果找不到DLL文檔,VB將產生一個運行時異常。開發人員可以捕獲該異常並且進行適當的處理。

CC++[編輯]

微軟Visual C++(MSVC)提供了許多標準C++的擴展,它允許直接在C++代碼中將函數標爲輸入還是輸出函數;這種做法已經被其它的Windows平臺的C和C++編譯器所採納,其中包括Windows版的GCC。這種擴展在函數聲明前使用__declspec屬性。如果是遵從C命名規範(convention)的外部名字,它們必須在C++代碼中聲明爲extern "C"以避免它們使用C++命名規範。

除了使用__declspec屬性定義輸入輸出函數之外,它們也可以列在項目DEF文檔的IMPORT或者EXPORTS部分。DEF文檔由鏈接器而不是編譯器進行處理,這樣它就不是C++特有的。

DLL的編譯將生成DLLLIB兩個文檔。LIB文檔是在編譯時用來鏈接DLL用的;它對於運行時鏈接不是必需的。除非你的DLL是一個COM服務器,DLL必須放在PATH環境變量、缺省系統路經或者是使用它的程序所在路徑三個的一個之內。COM服務器DLL使用regsvr32.exe註冊,它將DLL的路徑和全局唯一身份(GUID)記錄在註冊表中。應用程序能夠通過在註冊表中查找GUID、找到它的路徑從而使用這個DLL。

編程實例[編輯]

創建DLL輸出函數[編輯]

下面的例子展示了與特定語言相關的從DLL輸出符號表的方法。

Delphi

 library Example;
 
 // Function that adds two numbers
 function AddNumbers(a, b: Double): Double; cdecl;
 begin
     AddNumbers := a + b
 end;
 
 // Export this function
 exports
     AddNumbers;
 
 // DLL initialization code: no special handling needed
 begin
 end.

C 或 C++

 #include <windows.h>
 
 // Export this function
 extern "C" __declspec(dllexport) double AddNumbers(double a, double b);
 
 // DLL initialization function
 BOOL APIENTRY DllMain(HANDLE hModule, [[DWORD]] dwReason, LPVOID lpReserved)
 {
 	return TRUE;
 }
 
 // Function that adds two numbers
 double AddNumbers(double a, double b)
 {
 	return a + b;
 }

使用DLL輸入[編輯]

下面的例子展示了與特定語言相關的如何在編譯時鏈接DLL輸入符號表的方法。

Delphi
 program Example;
 {$APPTYPE CONSOLE}
 
 // Import function that adds two numbers
 function AddNumbers(a, b: Double): Double; cdecl; external 'Example.dll';
 
 var result: Double;
 begin
 result := AddNumbers(1, 2);
 Writeln('The result was: ', result)
 end.

C 或 C++

 #include <windows.h>
 #include <stdio.h>
 
 // Import function that adds two numbers
 extern "C" __declspec(dllimport) double AddNumbers(double a, double b);
 
 int main(int argc, char **argv)
 {
 	double result = AddNumbers(1, 2);
 	printf("The result was: %f\n", result);
 	return 0;
 }

運行時使用顯式調用[編輯]

下面的例子展示瞭如何使用不同語言特有的WIN32 API綁定進行運行時的調用和鏈接。

Microsoft Visual Basic

Option Explicit
Declare Function AddNumbers Lib "Example.dll" (ByVal a As Double, ByVal b As Double) As Double

Sub Main()
    Dim Result As Double
    Result = AddNumbers(1, 2)
    Debug.Print "The result was: " & Result
End Sub

C 或 C++

#include <windows.h>
#include <stdio.h>

// DLL function signature
typedef double (*importFunction)(double, double);

int main(int argc, char **argv)
{
    importFunction addNumbers;
    double result;

    // Load DLL file
    HINSTANCE hinstLib = LoadLibrary("Example.dll");
    if (hinstLib == NULL) {
        printf("ERROR: unable to load DLL\n");
        return 1;
    }

    // Get function pointer
    addNumbers = (importFunction)GetProcAddress(hinstLib, "AddNumbers");
    if (addNumbers == NULL) {
        printf("ERROR: unable to find DLL function\n");
        return 1;
    }

    // Call function.
    result = addNumbers(1, 2);

    // Unload DLL file
    FreeLibrary(hinstLib);

    // Display result
    printf("The result was: %f\n", result);

    return 0;
}

組件對象模型[編輯]

組件對象模型(COM)將DLL概念擴充到了面向對象編程。對象能夠從另外一個進程調用或者在另外一臺機器上運行。COM對象有一個唯一的GUID並且能夠實現強大的後臺以簡化如Visual Basic和ASP這樣的GUI前臺應用。它們也可以使用腳本語言編程。COM對象的創建和使用比DLL更爲複雜。

發佈了13 篇原創文章 · 獲贊 8 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章