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。

可看下面的例子(僅僅爲了說明問題):

  1. // X1.0 version  
  2. void func(int count)  
  3. {  
  4.     if(count < 0)  
  5.         count = 0;  
  6.     ....  
  7. }  
  8.   
  9. // X2.0 version  
  10. void func(int count)  
  11. {  
  12.     //負數處理被移除!  
  13.     ...  
  14. }  

一旦出現count爲負數的情況,則程序A在新版本的處理下就會有問題。

解決辦法Side-by-side Assembly,是windows Xp以及以上系統解決動態鏈接庫版本衝突所使用的技術,重點在於編譯程序時,由VS生成一個manifest文件,指明當前應用程序所使用的動態鏈接庫版本號;發佈程序時需同時發佈該manifest文件,供客戶計算機上的DLL Loader根據manifest加載適當版本的DLL,若不發佈該項manifest,客戶機則按默認版本加載DLL。下圖爲其典型的場景:



四、DLL與lib的關係

咋一看:lib是靜態鏈接庫;DLL是動態鏈接庫,一個編譯時提供;一個運行時提供,完了。

其實沒那麼簡單! lib也有靜態lib和動態lib之分

靜態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)

  1. // mydll.h file  
  2. extern "C" _declspec(dllexportint add(int a, int b);  
  3.   
  4. //mydll.cpp file  
  5. #include "mydll.h"  
  6. int add(int a, int b) //該DLL需要導出的函數功能:加法  
  7. {  
  8.     return a + b;  
  9. }  


說明:

(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

  1. #include<wtypes.h>   
  2. #include <winbase.h>   
  3. #include <iostream>  
  4. _declspec(dllimportint Add(int a, int b); //導入聲明,亦可以不加,如果加上可加快程序運行  
  5.   
  6. typedef int(*pAdd)(int a,int b);  
  7.   
  8. int main()  
  9. {  
  10.   
  11.     HINSTANCE hDLL;  
  12.     pAdd Add;  
  13.     hDLL=LoadLibrary("mydll.dll");  //加載 DLL文件  
  14.     if(hDLL == NULL)std::cout<<"Error!!!\n";  
  15.     Add=(pAdd)GetProcAddress(hDLL,"add");  //取DLL中的函數地址,以備調用  
  16.   
  17.     int a =Add(5,8);  
  18.     std::cout<<"a: "<<a<<std::endl;  
  19.       
  20.     FreeLibrary(hDLL);  
  21.     return 0;  
  22. }   


輸出結果:


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

  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.   
  10.     int a =add(5,8);  
  11.     std::cout<<"a: "<<a<<std::endl;  
  12.   
  13.     return 0;  
  14. }   

輸出:



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



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