DLL全稱dynamic linking library.即動態(tài)鏈接庫。廣泛應(yīng)用與windows及其他系統(tǒng)中。因此對dll的深刻了解,對計算機(jī)軟件開發(fā)專業(yè)人員來說非常重要。
windows中所有API都包含在DLL中。三個最重要的DLL是Kernel32.dll,User32.dll,GDI32.dll。
使用dll的好處:
1:擴(kuò)展了應(yīng)用程序的特性。
2:簡化了項(xiàng)目管理
可以讓不同的開發(fā)團(tuán)隊管理不同的模塊。
3:有助于節(jié)省內(nèi)存。
一個dll可被多個程序共享。多個程序調(diào)用同一個dll內(nèi)的同一個函數(shù)時,系統(tǒng)卻只需將該dll加載一次。
4:促進(jìn)資源共享。
5:促進(jìn)了本地化
可以使應(yīng)用程序只包含代碼但不包含用戶界面組件。
6:有助于解決平臺間差異。
使用延遲加載機(jī)制,程序僅僅加載需要的函數(shù),使程序可以在老版本的系統(tǒng)中運(yùn)行,可不是在某些函數(shù)不被兼容時拒絕運(yùn)行。
7:可以用于特殊目的。
如鉤子等等。
在應(yīng)用程序可以調(diào)用dll中的函數(shù)之前,必須將dll載入進(jìn)程地址空間??梢酝ㄟ^兩種方式實(shí)現(xiàn):一種是通過隱式載入時鏈接。另一種是顯式運(yùn)行時鏈接。接下來主要介紹隱式鏈接的過程。顯式鏈接在DLL高級技術(shù)中介紹。
在載入之前,必須要構(gòu)造DLL文件。下面我們來談?wù)刣ll的構(gòu)造過程。
1:創(chuàng)建一個頭文件。該頭文件包含我們想要在dll中導(dǎo)出的函數(shù)原型、結(jié)構(gòu)以及符號。構(gòu)建dll時所有的源文件都必須包含該頭文件。另外可執(zhí)行文件也需要該頭文件。
2:創(chuàng)建源文件來實(shí)現(xiàn)dll模塊中想要導(dǎo)出的函數(shù)和變量。該源文件在構(gòu)造可執(zhí)行文件時并不需要該源文件。
3:編譯器對每個源文件處理,并分別產(chǎn)生一個obj文件。
4:鏈接所有的obj模塊。產(chǎn)生獨(dú)立的dll映像文件。該文件在構(gòu)建可執(zhí)行文件時被使用。
5:如果dll文件中輸出了至少一個函數(shù)或變量,鏈接器還會生成lib文件。他只是列出了所有被導(dǎo)出的函數(shù)和變量的符號名。為了構(gòu)建可執(zhí)行模塊,在可執(zhí)行模塊代碼鏈接時,該文件也是必需的。
構(gòu)建可執(zhí)行模塊:
1:所有源文件中包含dll開發(fā)人員創(chuàng)建的dll的頭文件。
2:創(chuàng)建源文件。包含所有函數(shù)和變量。代碼中可以引用dll的函數(shù)和變量。
3:為每個源文件產(chǎn)生obj文件。
4:將所有obj文件鏈接,生成獨(dú)立的可執(zhí)行映像文件。該文件中包含所有二進(jìn)制代碼預(yù)計全局靜態(tài)變量。還包含一個導(dǎo)入段,列出了他需要的dll模塊的名稱,以及可執(zhí)行文件的二進(jìn)制代碼從中引用的函數(shù)和變量的符號名。
執(zhí)行:
加載程序?yàn)樾陆ㄟM(jìn)程申請一個地址空間區(qū)域,然后將可執(zhí)行模塊映射到地址空間中。加載程序解釋exe文件的導(dǎo)入段,對導(dǎo)入段中每個導(dǎo)入函數(shù)所在的dll,加載程序會在系統(tǒng)中對dll模塊進(jìn)行定位,并將該dll映射到進(jìn)程的地址空間中。如果dll需要從其他dll導(dǎo)入變量或函數(shù),其他dll也會被映射到進(jìn)程地址空間,執(zhí)行類似的操作。將所有dll映射到進(jìn)程地址空間后,就可以開始運(yùn)行了。
構(gòu)建dll模塊。
dll中通常只包含函數(shù)或變量,并不包含消息循環(huán)或創(chuàng)建窗口的代碼,因此創(chuàng)建dll文件相對容易。要注意在實(shí)際使用中,為了去掉代碼的抽象層,應(yīng)該避免從dll中導(dǎo)出變量。
首先應(yīng)該創(chuàng)建一個包含想要導(dǎo)出的變量或函數(shù)聲明的頭文件。所有dll的源文件都應(yīng)該包含這個文件,所有需要導(dǎo)入這些函數(shù)和變量的可執(zhí)行模塊的源文件也要包含該文件。
看例子:
可以看到在dll的頭文件中使用條件編譯#ifdef,對是否在源文件中對MYLIBAPI定義進(jìn)行了判斷。如果沒有定義的話就定義它為_declspec(import),這主要是在可執(zhí)行文件源文件中使用,目的是告訴編譯器應(yīng)該導(dǎo)入哪些符號或函數(shù)。這樣在需要引用該dll的可執(zhí)行模塊實(shí)現(xiàn)文件中就可以不定義_declspec(import).
代碼中出現(xiàn)了extern"C"修飾符,只有在編寫C++代碼時才應(yīng)該使用該修飾符。因?yàn)镃++編譯器會對函數(shù)名和變量名進(jìn)行改編,而C語言或其他語言不對變量名和函數(shù)名進(jìn)行改編。如果在創(chuàng)建dll的時候使用C++語言實(shí)現(xiàn),編譯器對函數(shù)名進(jìn)行了改編,而可執(zhí)行文件使用c語言實(shí)現(xiàn)的。當(dāng)可執(zhí)行文件引用dll中的變量或函數(shù)時,在鏈接時就會發(fā)現(xiàn)可執(zhí)行文件引用了一個不存在的符號。通過在C++源代碼中使用extern"C“修飾符,就告訴C++編譯器不要對函數(shù)或變量名進(jìn)行改編處理。這樣用C,C++或是任何語言編寫的可執(zhí)行模塊都可以訪問該變量或函數(shù)。
在鏈接dll的時候,鏈接器如果發(fā)現(xiàn)有函數(shù)或變量被導(dǎo)出,就會生成一個lib文件,該文件列出了該dll導(dǎo)出的符號。在鏈接任何可執(zhí)行模塊的時候只要可執(zhí)行模塊引用了該dll導(dǎo)出的符號,那么這個lib文件當(dāng)然是必須的。除了產(chǎn)生這個lib文件之外,在dll文件中還會被嵌入一個導(dǎo)出符號表。被稱為導(dǎo)入段,它列出了導(dǎo)出的變量、函數(shù)、和類的符號名,還會保存虛擬地址RVA,表示每個符號可以再dll的何處找到。
構(gòu)建可執(zhí)行模塊:
要想使該可執(zhí)行模塊源文件鏈接成功,還必須提供Mydll生成的lib文件。由于鏈接器需要將所有的obj文件鏈接到一起,它必須確定代碼中的哪個符號來自哪個dll,提供所有使用到的dll生成的lib文件是必須的。可用在客戶代碼中使用#pragma comment(lib,"../dll.lib")如果所有的符號都能被解決,那么將會生成可執(zhí)行模塊。
運(yùn)行可執(zhí)行模塊:
前面我們提過,啟動一個可執(zhí)行模塊時,系統(tǒng)加載程序會為進(jìn)程申請一塊地址空間,接著將可執(zhí)行文件模塊映射到進(jìn)程地址空間中,然后檢查可執(zhí)行文件的導(dǎo)入段,將所需的dll進(jìn)行定位并將它們映射到進(jìn)程的地址空間中。
由于導(dǎo)入段不包含路徑只包含名稱,所以將在程序必須按照特定的目錄搜索DLL文件。
以下是加載程序的搜索順序:
1:可執(zhí)行文件目錄。
2:windows系統(tǒng)目錄。
3:windows目錄的System目錄。
4:windows目錄。
5:進(jìn)程當(dāng)前目錄。
6:PATH環(huán)境變量所列出的目錄。
為了防止DLL偽造,windows進(jìn)行了設(shè)定,使對對windows目錄的搜索先于應(yīng)用程序的當(dāng)前目錄。此設(shè)置可以通過改變注冊表進(jìn)行改變。
更多關(guān)于dll的信息請參考另一篇博文:談?wù)刣ll高級技術(shù)。
參考自《windows核心編程》第五版第四部分,以上僅僅是個人總結(jié),如有紕漏,請不吝賜教。