共找到4條詞條名為動態鏈接庫的結果 展開

動態鏈接庫

編程相關名詞

動態鏈接庫(Dynamic Link Library或者Dynamic-link Library,縮寫為DLL),是微軟公司在微軟Windows操作系統中,實現共享函數庫概念的一種方式。這些庫函數的擴展名是”.dll"、".ocx"(包含ActiveX控制的庫)或者".drv"(舊式的系統驅動程序)。

介紹


動態鏈接提供了一種方法,使進程可以調用不屬於其可執行代碼的函數。函數的可執行代碼位於一個DLL文件中,該DLL包含一個或多個已被編譯、鏈接並與使用它們的進程分開存儲的函數。DLL還有助於共享數據和資源。多個應用程序可同時訪問內存中單個DLL副本的內容。
使用動態鏈接庫可以更為容易地將更新應用於各個模塊,而不會影響該程序的其他部分。例如,您有一個大型網路遊戲,如果把整個數百MB甚至數GB的遊戲的代碼都放在一個應用程序里,日後的修改工作將會十分費時,而如果把不同功能的代碼分別放在數個動態鏈接庫中,您無需重新生成或安裝整個程序就可以應用更新。
動態鏈接庫
動態鏈接庫
動態鏈接庫文件,是一種不可執行的二進位程序文件,它允許程序共享執行特殊任務所必需的代碼和其他資源。Windows提供的DLL文件中包含了允許基於Windows的程序在Windows環境下操作的許多函數和資源。一般被存放在電腦的"C:\Windows\System32"目錄下。
Windows中,DLL多數情況下是帶有".dll"擴展名的文件,但也可能是".ocx"或其他擴展名;Linux系統中常常是".so"的文件。它們向運行於Windows操作系統下的程序提供代碼、數據或函數。程序可根據DLL文件中的指令打開、啟用、查詢、禁用和關閉驅動程序。

背景


DLL的最初目的是節約應用程序所需的磁碟和內存空間。在一個傳統的非共享庫中,一部分代碼簡單地附加到調用的程序上。如果兩個程序調用同一個子程序,就會出現兩份那段代碼。相反,許多應用共享的代碼能夠切分到一個DLL中,在硬碟上存為一個文件,在內存中使用一個實例(instance)。DLL的廣泛應用使得早期的視窗能夠在緊張的內存條件下運行。
DLL提供了如模塊化這樣的共享庫的普通好處。模塊化允許僅僅更改幾個應用程序共享使用的一個DLL中的代碼和數據而不需要更改應用程序自身。這種模塊化的基本形式允許如Microsoft Office、Microsoft Visual Studio、甚至Microsoft Windows自身這樣大的應用程序使用較為緊湊的補丁和服務包。
模塊化的另外一個好處是插件的通用介面使用。單個的介面允許舊的模塊與新的模塊一樣能夠與以前的應用程序運行時無縫地集成到一起,而不需要對應用程序本身作任何更改。這種動態擴展的思想在ActiveX中發揮到了極致。
儘管有這麼多的優點,使用DLL也有一個缺點:DLL地獄,也就是幾個應用程序在使用同一個共享DLL庫發生版本衝突。這樣的衝突可以通過將不同版本的問題DLL放到應用程序所在的文件夾而不是放到系統文件夾來解決;但是,這樣將抵消共享DLL節約的空間。目前,Microsoft.NET將解決DLLhell問題當作自己的目標,它允許同一個共享庫的不同版本並列共存。由於現代的計算機有足夠的磁碟空間和內存,這也可以作為一個合理的實現方法。

特徵


內存管理
在Win32中,DLL文件按照片段(sections)進行組織。每個片段有它自己的屬性,如可寫或是只讀、可執行(代碼)或者不可執行(數據)等等。這些section可分為兩種,一個是與絕對地址定址無關的,所以能被多進程公用;另一個是與絕對地址定址有關的,這個就必須由每個進程有自己的副本專用。sections的這種二分類,在編譯DLL時就已經由編譯器、鏈接器給標註好了。所以在裝入DLL時,裝入器知道哪些sections在內存物理地址空間只需要有一份,供多個進程共用(映射到各個進程的內存邏輯地址空間,所以邏輯地址可以不同);哪些sections必須是進程使用自己的專用副本。
具體說,DLL裝入時需考慮下述情形:
1.局部變數——每個線程都有自己的棧,DLL內部的局部變數隨所在函數被執行而在各自線程的調用棧上開闢存儲空間。
2.全局變數
3.const全局變數——放入const節中,多進程共享;
4.非const全局變數——放入各個進程各自專用的data節中。即DLL裝入時各個進程複製一份自己專用的DLL的data節。但是,對於一個進程內的多個線程併發訪問這種進程空間全局變數,仍然存在線程安全問題。例如,在一個COM的DLL載入入一個進程的空間后,該進程的多個線程可能會併發訪問該COM庫的COM對象。為此,Windows與COM引入了線程“套間”(apartment)技術。一個進程內,應用程序與載入的各個DLL分屬於不同的Module,如果DLL使用所在Module的全局變數,例如動態鏈接MFC的regular dll在訪問自己的MFC全局變數時,應該明確聲明。
5.DLL內部定義的全局變數
6.訪問DLL以外定義的全局變數——使用間址技術,在DLL的data節中用一個指針數據類型的內存空間來保存一個外部全局變數的地址。
7.函數調用
8.調用DLL內部定義的函數。這不是問題。
9.調用DLL外部定義的函數。例如,DLL內部調用一個外部函數foo()。這個foo函數在進程1中可能實現為“四捨五入”,在進程2中實現為“下取整”。所以調用外部函數是各個進程私用的事情。解決辦法是使用間址技術,在data節中用一個“函數指針”數據類型的內存空間來保存這種外部函數的入口地址。
10.跳轉指令
11.DLL內部跳轉,不是問題
12.跳轉到DLL外部,解決同上述3.2
DLL代碼段通常被使用這個DLL的所有進程所共享。如果代碼段所佔據的物理內存被收回,它的內容就會被放棄,後面如果需要的話就直接從DLL文件重新載入。
與代碼段不同,DLL的數據段通常是私有的;也就是說,每個使用DLL的進程都有自己的DLL數據副本。作為選擇,數據段可以設置為共享,允許通過這個共享內存區域進行進程間通信。但是,因為用戶許可權不能應用到這個共享DLL內存,這將產生一個安全漏洞;也就是一個進程能夠破壞共享數據,這將導致其它的共享進程異常。例如,一個使用訪客賬號的進程將可能通過這種方式破壞其它運行在特權賬號的進程。這是在DLL中避免使用共享片段的一個重要原因。
當DLL被如UPX這樣一個可執行的packer壓縮時,它的所有代碼段都標記為可以讀寫並且是非共享的。可以讀寫的代碼段,類似於私有數據段,是每個進程私有的並且被頁面文件備份。這樣,壓縮DLL將同時增加內存和磁碟空間消耗,所以共享DLL應當避免使用壓縮DLL。
符號解析和綁定
DLL輸出的每個函數都由一個數字序號唯一標識,也可以由可選的名字標識。同樣,DLL引入的函數也可以由序號或者名字標識。對於內部函數來說,只輸出序號的情形很常見。對於大多數視窗API函數來說名字是不同視窗版本之間保留不變的;序號有可能會發生變化。這樣,我們不能根據序號引用視窗API函數。
按照序號引用函數並不一定比按照名字引用函數性能更好:DLL輸出表是按照名字排列的,所以對半查找可以用來在在這個表中根據名字查找這個函數。另外一方面,只有線性查找才可以用於根據序號查找函數。
將一個可執行文件綁定到一個特定版本的DLL也是可能的,這也就是說,可以在編譯時解析輸入函數(importedfunctions)的地址。對於綁定的輸入函數,連結工具保存了輸入函數綁定的DLL的時間戳和校驗和。在運行時Windows檢查是否正在使用同樣版本的庫,如果是的話,Windows將繞過處理輸入函數;否則如果庫與綁定的庫不同,Windows將按照正常的方式處理輸入函數。
綁定的可執行文件如果運行在與它們編譯所用的環境一樣,函數調用將會較快,如果是在一個不同的環境它們就等同於正常的調用,所以綁定輸入函數沒有任何的缺點。例如,所有的標準Windows應用程序都綁定到它們各自的Windows發布版本的系統DLL。將一個應用程序輸入函數綁定到它的目的環境的好機會是在應用程序安裝的過程。
運行時顯式鏈接
對每個DLL來說,Windows存儲了一個全域計數器,每多一個進程使用便多額外一個。LoadLibrary與FreeLibrary指令影響每一個進程內含的計數器;動態鏈接則不影響。因此藉由調用FreeLibrary多次,從存儲器卸載一DLL是很重要的。一個進程可以從它自己的VAS註銷此計數器。
DLL文件能夠在運行時使用LoadLibrary(或者LoadLibraryEx)API函數進行顯式調用,這個的過程微軟簡單地稱為運行時動態調用。API函數GetProcAddress根據查找輸出名稱符號、FreeLibrary卸載DLL。這些函數類似於POSIX標準API中的dlopen、dlsym、和dlclose。
注意微軟簡單稱為運行時動態鏈接的運行時隱式鏈接,如果不能找到鏈接的DLL文件,Windows將提示一個錯誤消息並且調用應用程序失敗。應用程序開發人員不能通過編譯鏈接來處理這種缺少DLL文件的隱式鏈接問題。另外一方面,對於顯式鏈接,開發人員有機會提供一個完善的出錯處理機制。
運行時顯式鏈接的過程在所有語言中都是相同的,因為它依賴於Windows API而不是語言結構。

優點


1.擴展了應用程序的特性;
2.可以用許多種編程語言來編寫;
3.簡化了軟體項目的管理;
4.有助於節省內存;
5.有助於資源共享;
6.有助於應用程序的本地化;
7.有助於解決平台差異;
8.可以用於一些特殊的目的。Windows使得某些特性只能為DLL所用。

依賴項


當某個程序或DLL使用其他DLL中的DLL函數時,就會創建依賴項。因此,該程序就不再是獨立的,並且如果該依賴項被損壞,該程序就可能遇到問題。例如,如果發生下列操作之一,則該程序可能無法運行:
• 依賴DLL升級到新版本。
• 修復了依賴DLL。
• 依賴DLL被其早期版本覆蓋。
• 從計算機中刪除了依賴DLL。
這些操作通常稱為DLL衝突。如果沒有強制實現向後兼容性,則該程序可能無法成功運行。

入口點


在創建DLL時,可以有選擇地指定入口點函數。當進程或線程將它們自身附加到DLL或者將它們自身從DLL分離時,將調用入口點函數。您可以使用入口點函數根據DLL的需要來初始化數據結構或者銷毀數據結構。此外,如果應用程序是多線程的,則可以在入口點函數中使用線程本地存儲(TLS)來分配各個線程專用的內存。下面的代碼是一個DLL入口點函數的示例:
當入口點函數返回FALSE值時,如果您使用的是載入時動態鏈接,則應用程序不啟動。如果您使用的是運行時動態鏈接,則只有個別DLL不會載入。
入口點函數只應執行簡單的初始化任務,不應調用任何其他DLL載入函數或終止函數。例如,在入口點函數中,不應直接或間接調用LoadLibrary函數或LoadLibraryEx函數。此外,不應在進程終止時調用FreeLibrary函數。
注意:在多線程應用程序中,請確保將對DLL全局數據的訪問進行同步(線程安全),以避免可能的數據損壞。為此,請使用TLS為各個線程提供唯一的數據。

如何導出


要導出DLL函數,您可以嚮導出的DLL函數中添加函數關鍵字,也可以創建模塊定義文件(.def)以列出導出的DLL函數。
(1)嚮導出的DLL函數中添加函數關鍵字
要使用函數關鍵字,您必須使用以下關鍵字來聲明要導出的各個函數:
__declspec(dllexport)
要在應用程序中使用導出的DLL函數,您必須使用以下關鍵字來聲明要導入的各個函數:
__declspec(dllimport)
通常情況下,您最好使用一個包含define語句和ifdef語句的頭文件,以便分隔導出語句和導入語句。
(2)創建模塊定義文件以列出導出的DLL函數
使用模塊定義文件來聲明導出的DLL函數。當您使用模塊定義文件(.def)時,您不必嚮導出的DLL函數中添加函數關鍵字。在模塊定義文件中,您可以聲明DLL的LIBRARY語句和EXPORTS語句。

特別調用


關於特定情況下的調用,比如DLL函數中使用到了Win32API或者將C++生成的DLL供標準C語言使用,則需要注意以下一些情況:
如果使用到了Win32API,則應該使用關鍵字__stdcall
在將C++生成的DLL供標準C語言使用時,輸出文件需要用extern"C"修飾,否則不能被標準C語言調用。如果使用__stdcall調用方式,可能產生C不識別的修飾名,所以設置導出函數時要採用.def文件形式,而不是__declspec(dllexport)形式。後者會進行修飾名轉換,C語言無法識別函數。
下面的代碼是一個定義文件的示例。

示例


HelloWorld示例DLL和應用程序
在MicrosoftVisualC++6.0中,可以通過選擇“Win32動態鏈接庫”項目類型或“MFC應用程序嚮導(dll)”來創建DLL。下面的代碼是一個在VisualC++中通過使用“Win32動態鏈接庫”項目類型創建的DLL的示例。
下面的代碼是一個“Win32應用程序”項目的示例,該示例調用SampleDLL DLL中的導出DLL函數。
注意:在載入時動態鏈接中,您必須鏈接在生成SampleDLL項目時創建的SampleDLL.lib導入庫。
在運行時動態鏈接中,您應使用與以下代碼類似的代碼來調用SampleDLL.dll導出DLL函數。

DLL描述


kernel32.dll
低級內核函數。包含內存管理、任務管理、資源控制等函數。
user32.dll
與Windows管理有關的函數。消息、菜單、游標、計時器、通信和其他大多數非現實函數都可以從這裡找到。
gdi32.dll
圖形設備介面庫。與設備輸出有關的函數:大多數繪圖、顯示場景、圖元文件、坐標及其字體函數都可以從這裡找到。
comdlg32.dll/lz32.dll/version.dll
提供一些附加函數的庫,包括通用對話框、文件壓縮、版本控制的支持。
comctl32.dll
一個新的Windows控制項集合,比如TreeView和RichTextBox等等,最初這個是為了Windows95而製作的,但是也使用於NT下。
mapi32.dll
電子郵件的專用函數。
netapi32.dll
訪問和控制網路的函數。
odbc32.dll
ODBC功能的DLL。