轉載:https://blog.csdn.net/heyabo/article/details/8721611
轉載:https://www.cnblogs.com/jin521/p/5598529.html
一、概念
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。
可看下面的例子(僅僅為了說明問題):
// 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之分。
靜態lib:它將導出聲明(后面會講)和實現均放到lib中,編譯后所有代碼都嵌入到宿主程序中去。
動態lib:相當於一個h文件,它是對實現部分(.DLL)的導出部分的聲明。編譯后只是將導出聲明部分編譯到宿主程序中,運行時需要相應的DLL文件的支持,否則無法工作。當生成一個新的DLL時,也會有配套的lib產生(即二者需一起分發),此時的lib即為動態lib(后面會有還有實驗)。
五、如何生成一個DLL
在VS2012開發環境下,打開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)
// 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
#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(L"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 結合方式
#include<wtypes.h> #include <winbase.h> #include <iostream>#include "../MyDll/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文件),則會出錯: