Windows 動態連結庫 DLL 淺析
一、概念
DLL:Dynamic Link Library,即動態連結庫,這種庫包含了可由多個程式同時使用的程式碼和資料。
它是microsoft在windows作業系統中實現共享函式庫概念的一種實現方式。其中windows中 一些作為DLL實現的檔案有:
- ActiveX控制元件(.ocx)檔案:如windows上的日曆控制元件。
- 控制面板(.cpl)檔案:控制面板中的每一項都是一個專用的DLL。
- 裝置驅動程式(.drv)檔案:如控制列印到印表機的印表機驅動程式。
二、由來
DLL最初用於節約應用程式所需要的磁碟和記憶體空間。早前,在傳統的非共享庫中,一部分程式碼簡單地附加到呼叫的程式中。如果兩個程式同時呼叫同一個子程式,就會出現兩份那段程式碼。相反,許多應用共享的程式碼能夠切分到一個DLL中,在硬碟上存為一個文件,在記憶體中只需使用一個例項。
三、DLL的優缺點
優點:
(1)節省記憶體和程式碼重用:當多個程式使用同一個函式庫時,DLL可以減少在磁碟和實體記憶體中載入程式碼的重複量,且有助於程式碼的重用。
(2)模組化:DLL有助於促進模組式程式開發。模組化允許僅僅更改幾個應用程式共享使用的一個DLL中的程式碼和資料而不需要更改應用程式自身。這種模組話的基本形式允許如Microsoft Office、Microsoft Visual Studio、甚至windows自身這樣大的應用程式 使用較為緊湊的補丁和服務包。
缺點:
DLL Hell:即DLL地獄,指幾個應用程式在使用同一個共享的DLL庫時發生版本衝突。
究其原因,八個字:成也共用,敗也共用
主要有兩種情況:
設想這樣一個場景:程式A會使用1.0版本的動態連結庫X,則在程式A安裝到系統時,會同時安裝該1.0版本的動態連結庫X。假設另一個程式B也會使用到動態連結庫X,那麼程式B直接複製到硬碟中即可正常執行,因為動態連結庫已經存在於系統中。然而有一天,另一程式C也要使用動態連結庫X,但是由於程式C開發的時間較晚,其需要較新版本—2.0版本的動態連結庫X。則在程式C被安裝到系統時,2.0版本的動態連結庫X 也必須隨之安裝到系統中,此時系統中1.0版本的動態連結庫將被2.0版本所取代(替換)。
情況1:新版本的動態連結庫不相容舊版本。如,A何B需要X所提供的功能,在升級到2.0後,新版本的X竟然把此功能取消了(很難想象吧,呵呵但有時候就是如此….)。則此時雖然C能正常執行,但A和B均無法工作了。
情況2:新版本的動態連結庫相容舊版本,但是存在一個bug。
可看下面的例子(僅僅為了說明問題):
[cpp] view plain copy print?- // X1.0 version
- void func(int count)
- {
- if(count < 0)
- count = 0;
- ….
- }
- // X2.0 version
- void func(int count)
- {
- //負數處理被移除!
- …
- }
// X1.0 version
void func(int count)
{
if(count < 0)
count = 0;
....
}
// X2.0 version
void func(int count)
{
//負數處理被移除!
...
}
一旦出現count為負數的情況,則程式A在新版本的處理下就會有問題。
解決辦法:Side-by-side Assembly,是windows Xp以及以上系統解決動態連結庫版本衝突所使用的技術,重點在於編譯程式時,由VS生成一個manifest檔案,指明當前應用程式所使用的動態連結庫版本號;釋出程式時需同時釋出該manifest檔案,供客戶計算機上的DLL Loader根據manifest載入適當版本的DLL,若不釋出該項manifest,客戶機則按預設版本載入DLL。下圖為其典型的場景:
四、DLL與lib的關係
咋一看:lib是靜態連結庫;DLL是動態連結庫,一個編譯時提供;一個執行時提供,完了。
靜態lib:它將匯出宣告(後面會講)和實現均放到lib中,編譯後所有程式碼都嵌入到宿主程式中去。
動態lib:相當於一個h檔案,它是對實現部分(.DLL)的匯出部分的宣告。編譯後只是將匯出宣告部分編譯到宿主程式中,執行時需要相應的DLL檔案的支援,否則無法工作。當生成一個新的DLL時,也會有配套的lib產生(即二者需一起分發),此時的lib即為動態lib(後面會有還有實驗)。
五、如何生成一個DLL
在VC++6.0開發環境下,開啟File\New\Project選項,可以選擇Win32 Dynamic-Link Library或MFC AppWizard【dll】來以不同的方式建立Non-MFC DLL、Regular DLL、Extension DLL等不同種類的動態連結庫。下面以選擇Win32 Dynamic-Link Library方式來建立一個DLL(實現加法運算):
1、建立一個Win32 Dynamic-Link Library方式的空工程,取名為myDLL
2、分別新增標頭檔案(.h)和原始檔(.cpp)
[cpp] view plain copy print?- // mydll.h file
- extern“C” _declspec(dllexport) int add(int a, int b);
- //mydll.cpp file
- #include “mydll.h”
- int add(int a, int b) //該DLL需要匯出的函式功能:加法
- {
- return a + b;
- }
// mydll.h file
extern "C" _declspec(dllexport) int add(int a, int b);
//mydll.cpp file
include "mydll.h"
int add(int a, int b) //該DLL需要匯出的函式功能:加法
{
return a + b;
}
說明:
(1)前面的 extern “C” 告訴編譯器函式可以在本模組或其他模組中使用,其中“C”表明需按照C語言方式編譯和連線它,因為C++編譯時,會對函式名進行修飾,用於實現函式過載,而C裡面沒有這個功能,所以需要用extern “C”在標頭檔案進行宣告的時候加以區分,以便連結時能進行正確地函式名查詢。
(2)_declspec(dllexport)為匯出函式關鍵字,意為需從DLL中匯出該函式,以便使用。
3、編譯連線
在進行編譯連線後會在Debug目錄下找到DLL檔案和對應的lib檔案
六、如何呼叫一個DLL
下面實現兩種呼叫方式:單獨.dll 和.h + .lib + .dll結合
注:需把對應的 .dll 檔案以及.lib 檔案和.h檔案(結合方式時)拷貝至呼叫的程式目錄下
(1)單純使用.dll
[cpp] view plain copy print?- #include<wtypes.h>
- #include <winbase.h>
- #include <iostream>
- _declspec(dllimport) int Add(int a, int b); //匯入宣告,亦可以不加,如果加上可加快程式執行
- typedefint(*pAdd)(int a,int b);
- int main()
- {
- HINSTANCE hDLL;
- pAdd Add;
- hDLL=LoadLibrary(”mydll.dll”); //載入 DLL檔案
- if(hDLL == NULL)std::cout<<“Error!!!\n”;
- Add=(pAdd)GetProcAddress(hDLL,”add”); //取DLL中的函式地址,以備呼叫
- int a =Add(5,8);
- std::cout<<”a: ”<<a<<std::endl;
- FreeLibrary(hDLL);
- return 0;
- }
#include<wtypes.h>
include <winbase.h>
include <iostream>
_declspec(dllimport) int Add(int a, int b); //匯入宣告,亦可以不加,如果加上可加快程式執行
typedef int(*pAdd)(int a,int b);
int main()
{
HINSTANCE hDLL;
pAdd Add;
hDLL=LoadLibrary("mydll.dll"); //載入 DLL檔案
if(hDLL == NULL)std::cout<<"Error!!!\n";
Add=(pAdd)GetProcAddress(hDLL,"add"); //取DLL中的函式地址,以備呼叫
int a =Add(5,8);
std::cout<<"a: "<<a<<std::endl;
FreeLibrary(hDLL);
return 0;
}
輸出結果:
(2).h + .lib + .dll 結合方式
[cpp] view plain copy print?- #include<wtypes.h>
- #include <winbase.h>
- #include <iostream>
- #include “mydll.h”
- #pragma comment(lib,”mydll.lib”) //將mydll.lib庫檔案連線到目標檔案中(即本工程)
- extern“C”_declspec(dllimport) int add(int a,int b);
- int main()
- {
- int a =add(5,8);
- std::cout<<”a: ”<<a<<std::endl;
- return 0;
- }
#include<wtypes.h>
include <winbase.h>
include <iostream>
include "mydll.h"
pragma comment(lib,"mydll.lib") //將mydll.lib庫檔案連線到目標檔案中(即本工程)
extern "C"_declspec(dllimport) int add(int a,int b);
int main()
{
int a =add(5,8);
std::cout<<"a: "<<a<<std::endl;
return 0;
}
輸出:
反例演示:此時如果去掉 .dll 檔案(即只有.lib 和 .h檔案),則會出錯:
參考文獻:
</div>
</div>