什么是動(dòng)態(tài)鏈接庫?
一、動(dòng)態(tài)鏈接庫的概念
動(dòng)態(tài)鏈接庫(Dynamic Link Library,縮寫為DLL)是一個(gè)可以被其它應(yīng)用程序共享的程序模塊,其中封裝了一些可以被共享的例程和資源。動(dòng)態(tài)鏈接庫文件的擴(kuò)展名一般是dll,也有可能是drv、sys和fon,它和可執(zhí)行文件(exe)非常類似,區(qū)別在于DLL中雖然包含了可執(zhí)行代碼卻不能單獨(dú)執(zhí)行,而應(yīng)由Windows應(yīng)用程序直接或間接調(diào)用。
動(dòng)態(tài)鏈接是相對(duì)于靜態(tài)鏈接而言的。所謂靜態(tài)鏈接是指把要調(diào)用的函數(shù)或者過程鏈接到可執(zhí)行文件中,成為可執(zhí)行文件的一部分。換句話說,函數(shù)和過程的代碼就在程序的exe文件中,該文件包含了運(yùn)行時(shí)所需的全部代碼。當(dāng)多個(gè)程序都調(diào)用相同函數(shù)時(shí),內(nèi)存中就會(huì)存在這個(gè)函數(shù)的多個(gè)拷貝,這樣就浪費(fèi)了寶貴的內(nèi)存資源。而動(dòng)態(tài)鏈接所調(diào)用的函數(shù)代碼并沒有被拷貝到應(yīng)用程序的可執(zhí)行文件中去,而是僅僅在其中加入了所調(diào)用函數(shù)的描述信息(往往是一些重定位信息)。僅當(dāng)應(yīng)用程序被裝入內(nèi)存開始運(yùn)行時(shí),在Windows的管理下,才在應(yīng)用程序與相應(yīng)的DLL之間建立鏈接關(guān)系。當(dāng)要執(zhí)行所調(diào)用DLL中的函數(shù)時(shí),根據(jù)鏈接產(chǎn)生的重定位信息,Windows才轉(zhuǎn)去執(zhí)行DLL中相應(yīng)的函數(shù)代碼。
一般情況下,如果一個(gè)應(yīng)用程序使用了動(dòng)態(tài)鏈接庫,Win32系統(tǒng)保證內(nèi)存中只有DLL的一份復(fù)制品,這是通過內(nèi)存映射文件實(shí)現(xiàn)的。DLL首先被調(diào)入Win32系統(tǒng)的全局堆棧,然后映射到調(diào)用這個(gè)DLL的進(jìn)程地址空間。在Win32系統(tǒng)中,每個(gè)進(jìn)程擁有自己的32位線性地址空間,如果一個(gè)DLL被多個(gè)進(jìn)程調(diào)用,每個(gè)進(jìn)程都會(huì)收到該DLL的一份映像。與16位Windows不同,在Win32中DLL可以看作是每個(gè)進(jìn)程自己的代碼。
二、動(dòng)態(tài)鏈接庫的優(yōu)點(diǎn)
1. 共享代碼、資源和數(shù)據(jù)
使用DLL的主要目的就是為了共享代碼,DLL的代碼可以被所有的Windows應(yīng)用程序共享。
2. 隱藏實(shí)現(xiàn)的細(xì)節(jié)
DLL中的例程可以被應(yīng)用程序訪問,而應(yīng)用程序并不知道這些例程的細(xì)節(jié)。
3. 拓展開發(fā)工具如Delphi的功能
由于DLL是與語言無關(guān)的,因此可以創(chuàng)建一個(gè)DLL,被C++、VB或任何支持動(dòng)態(tài)鏈接庫的語言調(diào)用。這樣如果一種語言存在不足,就可以通過訪問另一種語言創(chuàng)建的DLL來彌補(bǔ)。
三、動(dòng)態(tài)鏈接庫的實(shí)現(xiàn)方法
1. Load-time Dynamic Linking
這種用法的前提是在編譯之前已經(jīng)明確知道要調(diào)用DLL中的哪幾個(gè)函數(shù),編譯時(shí)在目標(biāo)文件中只保留必要的鏈接信息,而不含DLL函數(shù)的代碼;當(dāng)程序執(zhí)行時(shí),利用鏈接信息加載DLL函數(shù)代碼并在內(nèi)存中將其鏈接入調(diào)用程序的執(zhí)行空間中,其主要目的是便于代碼共享。
2. Run-time Dynamic Linking
這種方式是指在編譯之前并不知道將會(huì)調(diào)用哪些DLL函數(shù),完全是在運(yùn)行過程中根據(jù)需要決定應(yīng)調(diào)用哪個(gè)函數(shù),并用LoadLibrary和GetProcAddress動(dòng)態(tài)獲得DLL函數(shù)的入口地址。
教你認(rèn)識(shí)動(dòng)態(tài)鏈接庫DLL文件
DLL是Dynamic Link Library的縮寫,意為動(dòng)態(tài)鏈接庫。在Windows中,許多應(yīng)用程序并不是一個(gè)完整的可執(zhí)行文件,它們被分割成一些相對(duì)獨(dú)立的動(dòng)態(tài)鏈接庫,即DLL文件,放置于系統(tǒng)中。當(dāng)我們執(zhí)行某一個(gè)程序時(shí),相應(yīng)的DLL文件就會(huì)被調(diào)用。一個(gè)應(yīng)用程序可有多個(gè)DLL文件,一個(gè)DLL文件也可能被幾個(gè)應(yīng)用程序所共用,這樣的DLL文件被稱為共享DLL文件。DLL文件一般被存放在C:WindowsSystem目錄下。
1、如何了解某應(yīng)用程序使用哪些DLL文件
右鍵單擊該應(yīng)用程序并選擇快捷菜單中的“快速查看”命令,在隨后出現(xiàn)的“快速查看”窗口的“引入表”一欄中你將看到其使用DLL文件的情況。
2、如何知道DLL文件被幾個(gè)程序使用
運(yùn)行Regedit,進(jìn)入HKEY_LOCAL_MACHINESoftwareMicrosrftWindowsCurrent-
VersionSharedDlls子鍵查看,其右邊窗口中就顯示了所有DLL文件及其相關(guān)數(shù)據(jù),其中數(shù)據(jù)右邊小括號(hào)內(nèi)的數(shù)字就說明了被幾個(gè)程序使用,(2)表示被兩個(gè)程序使用,(0)則表示無程序使用,可以將其刪除。
3、如何解決DLL文件丟失的情況
有時(shí)在卸載文件時(shí)會(huì)提醒你刪除某個(gè)DLL文件可能會(huì)影響其他應(yīng)用程序的運(yùn)行。所以當(dāng)你卸載軟件時(shí),就有可能誤刪共享的DLL文件。一旦出現(xiàn)了丟失DLL文件的情況,如果你能確定其名稱,可以在Sysbckup(系統(tǒng)備份文件夾)中找到該DLL文件,將其復(fù)制到System文件夾中。如果這樣不行,在電腦啟動(dòng)時(shí)又總是出現(xiàn)“***dll文件丟失……”的提示框,你可以在“開始/運(yùn)行”中運(yùn)行Msconfig,進(jìn)入系統(tǒng)配置實(shí)用程序?qū)υ捒蛞院?,單擊選擇“System.ini”標(biāo)簽,找出提示丟失的DLL文件,使其不被選中,這樣開機(jī)時(shí)就不會(huì)出現(xiàn)錯(cuò)誤提示了。
什么是DLL
DLL是一個(gè)包含可由多個(gè)程序同時(shí)使用的代碼和數(shù)據(jù)的集合。例如,在Windows操作系統(tǒng)中,Comdlg32 DLL執(zhí)行與對(duì)話框有關(guān)的常見函數(shù)。因此,每個(gè)程序都可以使用該DLL中包含的功能來實(shí)現(xiàn)“打開”對(duì)話框。這有助于促進(jìn)代碼重用和內(nèi)存的有效使用。
通過使用DLL,程序可以實(shí)現(xiàn)模塊化,由相對(duì)獨(dú)立的組件組成。例如,一個(gè)計(jì)帳程序可以按模塊來銷售??梢栽谶\(yùn)行時(shí)將各個(gè)模塊加載到主程序中(如果安裝了相應(yīng)模塊)。因?yàn)槟K是彼此獨(dú)立的,所以程序的加載速度更快,而且模塊只在相應(yīng)的功能被請(qǐng)求時(shí)才加載。
DLL的優(yōu)點(diǎn)
1. 使用較少的資源。當(dāng)多個(gè)程序使用同一個(gè)函數(shù)庫時(shí),DLL可以減少在磁盤和物理內(nèi)存中加載的代碼的重復(fù)量。這不僅可以大大影響在前臺(tái)運(yùn)行的程序,而且可以大大影響其他在Windows操作系統(tǒng)上運(yùn)行的程序。
2. 簡化部署和安裝。當(dāng)DLL中的函數(shù)需要更新或修復(fù)時(shí),只要函數(shù)的參數(shù)和返回值沒有更改,就不需重新編譯或重新建立程序與該DLL的鏈接。此外,如果多個(gè)程序使用同一個(gè)DLL,那么多個(gè)程序都將從該更新或修復(fù)中獲益。
3. 支持多語言程序。只要程序遵循函數(shù)的調(diào)用約定,用不同編程語言編寫的程序就可以調(diào)用相同的DLL函數(shù)。程序與DLL函數(shù)在下列方面必須是兼容的:函數(shù)期望其參數(shù)被推送到堆棧上的順序,是函數(shù)還是應(yīng)用程序負(fù)責(zé)清理堆棧,以及寄存器中是否傳遞了任何參數(shù)。
4. 使國際版本的創(chuàng)建輕松完成。通過將資源放到DLL中,創(chuàng)建應(yīng)用程序的國際版本變得容易得多??蓪⒂糜趹?yīng)用程序的每個(gè)語言版本的字符串放到單獨(dú)的DLL資源文件中,并使不同的語言版本加載合適的資源。
DLL的類型(Kinds of DLLs)
Visual C++支持三種類型的DLL,它們分別是Non-MFC DLL、MFC Regular DLL、MFC Extension DLL。
1. Non-MFC DLL指的是不用MFC的類庫結(jié)構(gòu),直接用C語言寫的DLL,其導(dǎo)出的函數(shù)是標(biāo)準(zhǔn)的C接口,能被MFC或非MFC編寫的客戶程序調(diào)用。
2. Extension DLL支持C++接口,也就是說它導(dǎo)出C++函數(shù)或者整個(gè)類給客戶程序。導(dǎo)出函數(shù)可以使用C++或MFC的數(shù)據(jù)形式作為參數(shù)或返回值,當(dāng)導(dǎo)出整個(gè)類時(shí),客戶程序可以創(chuàng)建此類的對(duì)象或者從這些類進(jìn)行派生。使用Extension DLL的一個(gè)問題就是該DLL僅能和MFC客戶程序一起工作。
3. Regular DLL和上述的Extension Dll一樣,也是用MFC類庫編寫的,它的一個(gè)明顯的特點(diǎn)是在源文件里有一個(gè)繼承CWinApp的類(注意:此類DLL雖然從CWinApp派生,但沒有消息循環(huán))。Regular DLL有一個(gè)很大的限制就是,它可以導(dǎo)出C風(fēng)格的函數(shù),但不能導(dǎo)出C++類、成員函數(shù)或重載函數(shù)。調(diào)用Regular DLL的客戶程序不必是MFC應(yīng)用程序。
它們可以是在Visual C++、Dephi、Visual Basic等編譯環(huán)境下開發(fā)的客戶程序。
DLL的加載
客戶程序使用DLL可以采用兩種方式:一種是隱式鏈接,另一種是顯式鏈接。
1. 隱式鏈接(靜態(tài)加載或加載時(shí)動(dòng)態(tài)鏈接)
為了隱式鏈接到DLL,客戶程序必須從DLL的提供程序獲取下列各項(xiàng):
a. 包含導(dǎo)出函數(shù)或C++類聲明的頭文件(.h文件)
b. 要鏈接的導(dǎo)入庫(.lib文件)
c. 實(shí)際的DLL(.dll文件)
使用DLL的客戶程序必須include頭文件(此頭文件包含每個(gè)DLL中的導(dǎo)出函數(shù)或C++類),并且鏈接到此DLL的創(chuàng)建者所提供的導(dǎo)入庫。
-
- ...
- DLLAPI int Sum(int a, int b)
- {
- return a + b;
- }
-
-
- #ifdef CACL_EXPORTS
- #define DLLAPI __declspec(dllexport)
- #else
- #define DLLAPI __declspec(dllimport)
- #endif
-
- DLLAPI int Sum(int a, int b);
-
-
- DLLAPI int sum(int a, int b);
- ...
// Cacl.cpp...DLLAPI int Sum(int a, int b){return a + b;}// Cacl.h#ifdef CACL_EXPORTS#define DLLAPI __declspec(dllexport)#else#define DLLAPI __declspec(dllimport)#endifDLLAPI int Sum(int a, int b);// Client.cppDLLAPI int sum(int a, int b);...
2. 顯式鏈接(動(dòng)態(tài)加載或運(yùn)行時(shí)動(dòng)態(tài)鏈接)
在顯式鏈接下,客戶程序必須進(jìn)行函數(shù)調(diào)用以在運(yùn)行時(shí)顯式加載DLL。為顯式鏈接到DLL,客戶程序必須:
a. 調(diào)用LoadLibrary加載DLL和獲取模塊句柄
b. 調(diào)用GetProcAddress獲取指向客戶程序要調(diào)用的每個(gè)導(dǎo)出函數(shù)的函數(shù)指針(由于客戶程序是通過指針調(diào)用DLL的函數(shù),編譯器不生成外部引用,故無需與導(dǎo)入庫鏈接)
c. 使用完DLL后調(diào)用FreeLibrary釋放資源
-
- HINSTANCE hDLL = LoadLibrary("demo");
- if (hDLL != NULL)
- {
- LPFNDLLFUNC1 lpfnDllFunc1 = GetProcAddress(hDLL, "Sum");
- if (lpfnDllFunc1 == NULL)
- {
- FreeLibrary(hDLL);
- return SOME_ERROR_CODE;
- }
- return lpfnDllFunc1(dwParam, uParam);
- }
// Client.cppHINSTANCE hDLL = LoadLibrary("demo");if (hDLL != NULL){LPFNDLLFUNC1 lpfnDllFunc1 = GetProcAddress(hDLL, "Sum");if (lpfnDllFunc1 == NULL){FreeLibrary(hDLL);return SOME_ERROR_CODE;}return lpfnDllFunc1(dwParam, uParam);}
客戶程序如何找到DLL
如果用LoadLibrary顯示鏈接到DLL的話,我們可以指定DLL的全路徑名。如果沒有指定路徑名,或者用了隱式鏈接,則Windows將使用下面的搜索序列來定位DLL:
1. 包含客戶EXE文件的目錄
2. 當(dāng)前目錄
3. Windows系統(tǒng)目錄(GetSystemDirectory)
4. Windows目錄(GetWindowsDirectory)
5. 在Path環(huán)境變量里列出的目錄(注意:未使用LIBPATH環(huán)境變量)
導(dǎo)出DLL函數(shù)
DLL文件的布局與EXE文件非常相似,但有一個(gè)重要差異:DLL文件包含導(dǎo)出表。導(dǎo)出表包含DLL導(dǎo)出到客戶程序的每個(gè)函數(shù)的名稱。只有導(dǎo)出表中的函數(shù)可由客戶程序訪問。DLL中的任何其他函數(shù)都是DLL私有的。通過使用帶/EXPORTS選項(xiàng)的Dumpbin工具,可以查看DLL的導(dǎo)出表。
有兩種從DLL導(dǎo)出函數(shù)的方法:
1. 在函數(shù)的定義中使用__declspec(dllexport)關(guān)鍵字
使用__declspec(dllexport)關(guān)鍵字可以從DLL中導(dǎo)出數(shù)據(jù)、函數(shù)、類或類成員函數(shù)。如果要導(dǎo)出函數(shù),__declspec(dllexport)關(guān)鍵字必須出現(xiàn)在調(diào)用約定關(guān)鍵字()的左邊(如果指定了關(guān)鍵字)。例如:
- __declspec(dllexport) void __cdecl Function();
__declspec(dllexport) void __cdecl Function();
若要導(dǎo)出類中的所有公共數(shù)據(jù)成員和成員函數(shù),關(guān)鍵字必須出現(xiàn)在類名的左邊,如下所示:
- class __declspec(dllexport) CExampleExport : public CObject
- {
- ...
- };
class __declspec(dllexport) CExampleExport : public CObject{...};
2. 在生成DLL時(shí),創(chuàng)建一個(gè)模塊定義(.def)文件并使用此DEF文件。(如果希望按序號(hào)而不是按名稱從DLL導(dǎo)出函數(shù),則使用此方法。)
示例DLL和客戶程序
- // Cacl.h
- #ifdef CACL_EXPORTS
- #define DLLAPI __declspec(dllexport)
- #else
- #define DLLAPI __declspec(dllimport)
- #endif
-
- DLLAPI int Sum(int a, int b);
-
- // Cacl.cpp
- #include <windows.h>
- #include "Cacl.h"
-
- BOOL APIENTRY DllMain(HANDLE hModule, DWORD reason, LPVOID lpReserved)
- {
- switch (reason)
- {
- case DLL_PROCESS_ATTACH:
- case DLL_THREAD_ATTACH:
- case DLL_THREAD_DETACH:
- case DLL_PROCESS_DETACH:
- break;
- }
- return TRUE;
- }
-
- DLLAPI int Sum(int a, int b)
- {
- return a + b;
- }
-
- // Client.cpp
- #include <stdio.h>
- #include "Cacl.h"
-
- int main(int argc, char* argv[])
- {
- printf("Sum = %d\n", Sum(5, 3));
- return 0;
- }
// Cacl.h#ifdef CACL_EXPORTS#define DLLAPI __declspec(dllexport)#else#define DLLAPI __declspec(dllimport)#endifDLLAPI int Sum(int a, int b);// Cacl.cpp#include <windows.h>#include "Cacl.h"BOOL APIENTRY DllMain(HANDLE hModule, DWORD reason, LPVOID lpReserved){switch (reason){case DLL_PROCESS_ATTACH:case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break;}return TRUE;}DLLAPI int Sum(int a, int b){return a + b;}// Client.cpp#include <stdio.h>#include "Cacl.h"int main(int argc, char* argv[]){printf("Sum = %d\n", Sum(5, 3));return 0;}
如何調(diào)試DLL
調(diào)試DLL很容易,只要從DLL工程啟動(dòng)調(diào)試器即可。第一次這樣做的時(shí)候,調(diào)試器會(huì)請(qǐng)求給出客戶EXE程序的路徑。之后,每次從調(diào)試器運(yùn)行DLL時(shí),調(diào)試器會(huì)自動(dòng)裝入客戶EXE程序,而EXE用搜索序列找到DLL。