Windows平台下动态链接库的总结
1、 動(dòng)態(tài)鏈接庫與靜態(tài)連接庫
? ? ? ?靜態(tài)連接庫與動(dòng)態(tài)鏈接庫都是經(jīng)過編譯器編譯之后的,在計(jì)算機(jī)上可以直接運(yùn)行的二進(jìn)制目標(biāo)文件,就像exe文件一樣,但不同于exe文件的是靜態(tài)鏈接庫和動(dòng)態(tài)鏈接庫不可以獨(dú)立運(yùn)行,一般而言,動(dòng)態(tài)鏈接庫和動(dòng)態(tài)鏈接是在內(nèi)部實(shí)現(xiàn)了一些功能,對(duì)外提供了一組接口函數(shù)使得外部的程序能夠通過這些對(duì)外的接口函數(shù)來使用其內(nèi)部的功能。
? ? ? ?靜態(tài)鏈接庫,是在調(diào)用該靜態(tài)庫的可執(zhí)行文件編譯的時(shí)候(更確切地說是鏈接的時(shí)候)就將其中的內(nèi)容整合進(jìn)目標(biāo)文件,而動(dòng)態(tài)鏈接庫則是在運(yùn)行或加載時(shí)才將動(dòng)態(tài)庫中的內(nèi)容整合進(jìn)目標(biāo)文件。
? ? ? ?靜態(tài)連接庫命名為XXX.lib,它在程序鏈接時(shí)候?qū)⒈徽线M(jìn)主程序,假如我們要開發(fā)一個(gè)計(jì)算器軟件,該軟件的主程序?yàn)镃alc.exe,在開發(fā)Calc的時(shí)候使用到了兩個(gè)靜態(tài)鏈接庫 add.lib和multi.lib ,其中靜態(tài)庫add.lib主要用于對(duì)各種類型數(shù)據(jù)的加、減操作,靜態(tài)庫multi.lib主要用于對(duì)各種類型數(shù)據(jù)的進(jìn)行高效率的乘法和除法操作。在開發(fā)Calc.exe的時(shí)候,我們就必須在編譯時(shí)用到這兩個(gè)靜態(tài)鏈接庫 add.lib和multi.lib,最終我們發(fā)布的程序只有calc.exe,沒有add.lib和multi.lib,因?yàn)樵谏赡繕?biāo)文件calc.exe的時(shí)候,編譯工具就已經(jīng)把這兩個(gè)靜態(tài)庫中的內(nèi)容整合進(jìn)了calc.exe中,這時(shí)候用戶拿到的計(jì)算器程序就一個(gè)文件:Calc.exe。因此靜態(tài)庫只是提供給開發(fā)人員使用,對(duì)于最終用戶來說是不可見的。
? ? ? ?動(dòng)態(tài)鏈接庫一般命名為XXX.dll,它在程序鏈接的時(shí)候不會(huì)被整合進(jìn)目標(biāo)文件,因此在發(fā)布應(yīng)用程序的時(shí)候,需要一并把所需的動(dòng)態(tài)庫文件提供給用戶。按照上一個(gè)例子,要開發(fā)名為Calc.exe的程序,但是在開發(fā)的時(shí)候用到了兩個(gè)動(dòng)態(tài)庫add.dll和multi.dll,其中add.dll主要用于對(duì)各種類型數(shù)據(jù)進(jìn)行高效的加減操作,add.dll主要用于對(duì)各類數(shù)據(jù)進(jìn)行高效的乘除操作,在編譯的時(shí)候,我們并不需要這兩個(gè)dll文件,dll文件是在運(yùn)行的時(shí)候需要的,如
果采用顯示動(dòng)態(tài)加載dll的方式,則只需要一個(gè)頭文件就可以了,如果要采用隱式加載時(shí)鏈接dll的方式,則還需要對(duì)應(yīng)的lib文件。在Calc的開發(fā)過程中,編譯器并沒有將dll中實(shí)現(xiàn)的內(nèi)容放進(jìn)Calc.exe中,因此我們程序在發(fā)布的時(shí)候,就不能只發(fā)布Calc.exe,還需把Calc.exe所依賴的動(dòng)態(tài)庫add.dll和multi.dll一起提供給用戶,這時(shí)候用戶拿到的計(jì)算器程序包含了三個(gè)文件:Calc.exe、add.dll和multi.dll,其中Calc.exe是可執(zhí)行程序,用戶只需雙擊它就可以使用。
2、創(chuàng)建動(dòng)態(tài)鏈接庫
? ? ? ?動(dòng)態(tài)庫的開發(fā)基本與正常程序開發(fā)一樣,只是它不需要main函數(shù),當(dāng)然dll里面也有個(gè)DllMain函數(shù),但是我們一般不使用它。創(chuàng)建動(dòng)態(tài)庫的一般步驟如下:
(1)??創(chuàng)建頭文件,在該頭文件中主要包含待導(dǎo)出的接口函數(shù),該頭文件需和動(dòng)態(tài)庫一起提供給使用者,因?yàn)樵谑褂脛?dòng)態(tài)庫的時(shí)候還需要包含此頭文件,這樣動(dòng)態(tài)庫的使用者才知道動(dòng)態(tài)庫的導(dǎo)出函數(shù)是怎么定義的。在windows下動(dòng)態(tài)庫中的函數(shù)如果不特殊說明默認(rèn)是不導(dǎo)出的,因此如果需要導(dǎo)出一個(gè)函數(shù),需要在函數(shù)的聲明時(shí)使用__declspec(dllexport)進(jìn)行告訴編譯器聲明的函數(shù)將被導(dǎo)出。
(2)??創(chuàng)建源文件,在源文件中來實(shí)現(xiàn)dll的所有功能。這些源文件不會(huì)提供給使用者。
(3)??編譯各源文件,將每個(gè)源文件都分別編譯成obj文件。
(4)??鏈接obj文件,連接起將所有的obj文件鏈接起來形成一個(gè)dll文件。
(5)??生成lib文件,當(dāng)動(dòng)態(tài)庫中導(dǎo)出的符號(hào)(這里的符號(hào)是指導(dǎo)出的函數(shù)或變量)超出一個(gè)時(shí),還會(huì)生成一個(gè)lib文件,這里的lib文件不像靜態(tài)庫中生成的lib文件(靜態(tài)庫的lib文件中包含了靜態(tài)庫的全部實(shí)現(xiàn)內(nèi)容),它里面沒有實(shí)現(xiàn)代碼,只是包含導(dǎo)出的符號(hào);動(dòng)態(tài)庫的實(shí)現(xiàn)代碼被放在dll中,lib文件中只是簡(jiǎn)單的包含導(dǎo)出的符號(hào)和其他一些鏈接信息,以供隱式使用動(dòng)態(tài)庫所用。
? ? ? ?下面以VS2008為例來創(chuàng)建一個(gè)Calc.exe所需要的動(dòng)態(tài)庫add.dll
(1)??File =>New =>Project,在彈出的對(duì)話框中依次進(jìn)入:?????
Visual C++ => Win32 => Win32 ConsoleApplication
在Name中填寫dll工程的名字add,在Location中選擇add功所要保存的路徑,點(diǎn)擊OK,在接下來的一個(gè)頁碼中不作修改直接點(diǎn)擊Next,進(jìn)入Application Settings頁面中選擇DLL(這里默認(rèn)的是Console application),然后點(diǎn)擊Finish,創(chuàng)建一個(gè)空的dll工程。此時(shí)可以看到VS已經(jīng)自動(dòng)生成了dlmain.cpp,在該文件中就有DllMain的默認(rèn)定義。這里暫時(shí)不做修改。
(2)??添加頭文件add.h,
#ifndef _ADD_H
#ifdef __cplusplus
? #defineMY_EXPORT extern “C”__declspec(dllexport)
#else
? # defineMY_EXPORT __declspec(dllexport)
#endif //__cplusplus
?
MY_EXPORT int add(int iVal1, int iVal2);
#endif //_ADD_H
? ? ? ?這里extern “C”是C和C++混合一起編程時(shí)需要使用的修飾符號(hào)。另外,編譯器看到__declspec(dllexport)符號(hào)時(shí),會(huì)為其所修飾的函數(shù)、類、變量等生成一些額外的信息,這些額外的信息在使用動(dòng)態(tài)庫的時(shí)候會(huì)用到。
(3)??添加源文件add.cpp,在源文件中添加導(dǎo)出函數(shù)的實(shí)現(xiàn)。
#include “add.h”
const int DEFAULT_ADD_VALUE = 100;
int add (int iVal1, int iVal2)
{
? ? ? ?return iVal1 + iVal2 + DEFAULT_ADD_VALUE;
}
(4)??Build 該dll工程,可以在其Debug或Release目錄下看到生成了兩個(gè)文件:add.dll和add.lib
在創(chuàng)建動(dòng)態(tài)鏈接庫的時(shí)候需要注意:
(1)??避免導(dǎo)出變量,導(dǎo)出變量將不利于動(dòng)態(tài)庫的維護(hù)和擴(kuò)展。
(2)??慎重導(dǎo)出類,因?yàn)橹挥挟?dāng)導(dǎo)出C++類的模塊使用的編譯器和導(dǎo)入C++類的編譯器為同一廠商提供時(shí),才可能保證沒有問題。
3、使用動(dòng)態(tài)庫連接庫
? ? ? ?動(dòng)態(tài)鏈接庫有兩種使用方式:“隱式載入時(shí)鏈接”和“顯示運(yùn)行時(shí)鏈接”。“隱式載入時(shí)鏈接”方式在程序啟動(dòng)時(shí)就需要把所有依賴的動(dòng)態(tài)庫載入,即時(shí)在程序中沒有執(zhí)行動(dòng)態(tài)庫中的代碼也需要將動(dòng)態(tài)庫加入,如果所依賴的動(dòng)態(tài)庫不存在,則程序無法啟動(dòng);“顯示運(yùn)行時(shí)鏈接”方式需要在程序中通過函數(shù)LoadLibrary來顯示的加載動(dòng)態(tài)庫,它是運(yùn)行時(shí)才加載所使用的動(dòng)態(tài)庫,如果程序中沒有執(zhí)行到加載動(dòng)態(tài)庫的操作,則動(dòng)態(tài)庫不會(huì)被加載進(jìn)來;例如:
boolbUseLib = false;
if(bUseLib)
{
?????????? HMODULE hdll = LoadLibrary(“add.dll”);
?????????? Typedef int (*TADD)(int,int);
?????????? TADD add = GetProcAddress(hdll, “add”);
?????????? cout<<add(12,13)<<endl;????
}
? ? ? ?此時(shí),在上面的代碼中動(dòng)態(tài)庫的加載不會(huì)被執(zhí)行,此時(shí)即使沒有add.dll程序也可以正常執(zhí)行。
相反如果在隱式動(dòng)態(tài)庫中,其實(shí)現(xiàn)代碼可能為如下形式:
boolbUseLib = false;
if(bUseLib)
{
? ?cout<<add(12,13)<<endl;????
}
? ? ? ?此時(shí),在程序剛啟動(dòng)時(shí)就會(huì)加載add.dll,在上述代碼中根本就走add這個(gè)分支,但是沒有了add.dll程序還是啟動(dòng)不了。
在VS2008中,隱式載入時(shí)鏈接的配置與使用方式,需要注意的是Debug和Release模式需要分別配置,配置文件也要對(duì)應(yīng),例如工程的Debug需要配置Debug模式的動(dòng)態(tài)庫,工程的Release模式需要配置Release模式的動(dòng)態(tài)庫:
[1]添加動(dòng)態(tài)庫的頭文件路徑:項(xiàng)目屬性=>C/C++=〉General=〉A(chǔ)dditional Include Directories,在其中填入動(dòng)態(tài)庫頭文件的路徑。
[2]配置lib文件的路徑:項(xiàng)目 屬性=>Linker=〉General=〉A(chǔ)dditional Library Directories,在其中填入lib文件的路徑。
[3]配置所依賴的lib文件名:項(xiàng)目 屬性=>Linker=〉Input=〉A(chǔ)dditionalDepedencies,在其中填入所依賴的lib文件名稱。
[4]將所依賴的dll文件拷貝到Debug或Release目錄下;或者將dll的路徑添加到環(huán)境變量中;或者將dll拷貝到windows下面system32目錄下。
[5]在工程中包含dll的頭文件,然后在程序中就可以直接使用dll中的函數(shù)了。
4、可執(zhí)行程序搜索動(dòng)態(tài)庫的順序
? ? ? ?在運(yùn)行時(shí),可執(zhí)行程序?qū)凑障旅娴捻樞騺硭阉鲃?dòng)態(tài)庫,搜索到所需要的動(dòng)態(tài)庫之后再進(jìn)行加載,下面以 Calc.exe、add.dll、multi.dll組成的計(jì)算器程序在執(zhí)行時(shí)加載動(dòng)態(tài)庫為例(假設(shè)這三個(gè)文件都放在Calc目錄下):
(1)??可執(zhí)行程序所在的目錄,如本例中Calc.exe所在的Calc目錄。
(2)??Windows的系統(tǒng)目錄,該目錄可通過函數(shù)GetSystemDirectory得到,在xp系統(tǒng)下就是C:\WINDOWS\system32
(3)??Windows的System32子目錄
(4)??Windows目錄,此目錄可以通過函數(shù)GetWindowsDirectory得到。
(5)??進(jìn)程的當(dāng)前目錄,注意進(jìn)程的當(dāng)前目錄不一定是啟動(dòng)目錄,在使用過程中有可能被更改。
(6)??環(huán)境變量path中所列出的目錄。
5、創(chuàng)建和使用動(dòng)態(tài)庫時(shí)需要注意的事項(xiàng)
(1)??跨編譯器使用dll時(shí)要防止導(dǎo)出符號(hào)的改名。假如dll的編譯與使用都是同一家廠商的編譯器,則不存在問題,如果用MS VC++開發(fā)的dll要用到其它非MSVC++的編譯器的項(xiàng)目上時(shí),需要采取一些預(yù)防措施來防止導(dǎo)出符號(hào)被改名,可以通過以下兩種方式來防止導(dǎo)出符號(hào)被改名:
[1]在編譯dll的時(shí)候增加一個(gè).def文件,并在該文件中添加EXPORT段,然后將導(dǎo)出的符號(hào)列在EXPORT之后即可,例如:
EXPORT
??????? add
[2]在編譯dll的時(shí)候,在源文件的開始添加類似如下的代碼:
#pragmacomment(linker, ”/export:add=_add@8”)
這里_add@8是MS編譯器自動(dòng)為符號(hào)修改的名字,其修改規(guī)則是:在函數(shù)名前加”_”,在函數(shù)名后jia”@”和傳給函數(shù)的參數(shù)的字節(jié)數(shù)構(gòu)成。
(2)??開發(fā)過程中不要在一個(gè)動(dòng)態(tài)庫中申請(qǐng)內(nèi)存然后再這個(gè)動(dòng)態(tài)庫之外釋放,這樣不僅容易引起內(nèi)存泄露,而且還有可能由于申請(qǐng)和釋放內(nèi)存所使用的運(yùn)行庫不一樣而引起其它的問題。
(3)??盡量避免導(dǎo)出變量。
(4)??謹(jǐn)慎導(dǎo)出類。
(5)??在顯示動(dòng)態(tài)鏈接的時(shí)候可以通過LoadLibrary加載一個(gè)exe但是并不執(zhí)行該exe程序,這樣我們就可以像動(dòng)態(tài)庫一樣使用該exe文件中的資源了,不過此時(shí)需要指定動(dòng)態(tài)庫加載函數(shù)LoadLibrary的第二個(gè)參數(shù)Flags為:LOAD_LIBRARY_AS_DATAFILE
(6)??Dll由系統(tǒng)維護(hù),可以在各進(jìn)程之間共享代碼,因此,顯示動(dòng)態(tài)鏈接時(shí),每調(diào)用LoadLibrary加載一次dll,操作系統(tǒng)就會(huì)將對(duì)應(yīng)dll的計(jì)數(shù)器加1,因此一次LoadLibrary就需要對(duì)應(yīng)一次FreeLibrary來將計(jì)數(shù)器減1,在動(dòng)態(tài)庫加載時(shí)可以通過GetModuleHandle來獲取判斷一個(gè)動(dòng)態(tài)庫是否被加載,例如:
HMODULEhDll = GetModuleHandle(_T(“add.dll”));
If(NULL==hDll)
??????? hDll = LoadLibrary(_T(“add.dll”));
GetModule還有另外一個(gè)作用:返回應(yīng)用程序的可執(zhí)行文件的句柄,此時(shí)只需傳給它一個(gè)NULL參數(shù)即可。
總結(jié)
以上是生活随笔為你收集整理的Windows平台下动态链接库的总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: _ASSERTE(_CrtIsValid
- 下一篇: 小技巧教你解决此windows副本不是正