1. 程式人生 > >Windows 動態連結庫 DLL 淺析

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庫時發生版本衝突。

究其原因,八個字:成也共用,敗也共用

。因為DLL Hell正是由於動態連結庫可與其他程式共用函式、資源所導致。

主要有兩種情況

設想這樣一個場景:程式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?
  1. // X1.0 version
  2. void func(int count)  
  3. {  
  4.     if(count < 0)  
  5.         count = 0;  
  6.     ….  
  7. }  
  8. // X2.0 version
  9. void func(int count)  
  10. {  
  11.     //負數處理被移除!
  12.     …  
  13. }  
// 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(實現加法運算):


2、分別新增標頭檔案(.h)和原始檔(.cpp)

[cpp] view plain copy print?
  1. // mydll.h file
  2. extern“C” _declspec(dllexportint add(int a, int b);  
  3. //mydll.cpp file
  4. #include “mydll.h”
  5. int add(int a, int b) //該DLL需要匯出的函式功能:加法
  6. {  
  7.     return a + b;  
  8. }  
// 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?
  1. #include<wtypes.h> 
  2. #include <winbase.h> 
  3. #include <iostream>
  4. _declspec(dllimportint Add(int a, int b); //匯入宣告,亦可以不加,如果加上可加快程式執行
  5. typedefint(*pAdd)(int a,int b);  
  6. int main()  
  7. {  
  8.     HINSTANCE hDLL;  
  9.     pAdd Add;  
  10.     hDLL=LoadLibrary(”mydll.dll”);  //載入 DLL檔案
  11.     if(hDLL == NULL)std::cout<<“Error!!!\n”;  
  12.     Add=(pAdd)GetProcAddress(hDLL,”add”);  //取DLL中的函式地址,以備呼叫
  13.     int a =Add(5,8);  
  14.     std::cout<<”a: ”<<a<<std::endl;  
  15.     FreeLibrary(hDLL);  
  16.     return 0;  
  17. }   
#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&lt;&lt;"Error!!!\n";
Add=(pAdd)GetProcAddress(hDLL,"add");  //取DLL中的函式地址,以備呼叫

int a =Add(5,8);
std::cout&lt;&lt;"a: "&lt;&lt;a&lt;&lt;std::endl;

FreeLibrary(hDLL);
return 0;
}

輸出結果:


(2).h + .lib + .dll 結合方式

[cpp] view plain copy print?
  1. #include<wtypes.h> 
  2. #include <winbase.h> 
  3. #include <iostream>
  4. #include “mydll.h”
  5. #pragma comment(lib,”mydll.lib”)  //將mydll.lib庫檔案連線到目標檔案中(即本工程)
  6. extern“C”_declspec(dllimportint add(int a,int b);  
  7. int main()  
  8. {  
  9.     int a =add(5,8);  
  10.     std::cout<<”a: ”<<a<<std::endl;  
  11.     return 0;  
  12. }   
#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&lt;&lt;"a: "&lt;&lt;a&lt;&lt;std::endl;

return 0;

}

輸出:


反例演示:此時如果去掉 .dll 檔案(即只有.lib 和 .h檔案),則會出錯:


參考文獻

        </div>
            </div>