什么是DLL(動態(tài)鏈接庫),?DLL是一個包含可由多個程序同時使用的代碼和數(shù)據(jù)的庫,。例如:在Windows操作系統(tǒng)中,Comdlg32 DLL執(zhí)行與對話框有關(guān)的常見函數(shù),。因此,,每個程序都可以使用該DLL中包含的功能來實現(xiàn)“打開”對話框。這有助于促進(jìn)代碼重用和內(nèi)存的有效使用,。這篇文章的目的就是讓你一次性就能了解和掌握DLL,。 為什么要使用DLL(動態(tài)鏈接庫),?代碼復(fù)用是提高軟件開發(fā)效率的重要途徑。一般而言,,只要某部分代碼具有通用性,,就可以將它構(gòu)造成相對獨立的功能模塊并在之后的項目中重復(fù)使用。比較常見的例子是各種應(yīng)用程序框架,,它們都以源代碼的形式發(fā)布,。由于這種復(fù)用是源代碼級別的,源代碼完全暴露給了程序員,,因而稱之為“白盒復(fù)用”,。白盒復(fù)用有以下三個缺點:
為了彌補這些不足,就提出了“二進(jìn)制級別”的代碼復(fù)用了,。使用二進(jìn)制級別的代碼復(fù)用一定程度上隱藏了源代碼,,對于“黑盒復(fù)用”的途徑不只DLL一種,,靜態(tài)鏈接庫,,甚至更高級的COM組件都是。 使用DLL主要有以下優(yōu)點:
創(chuàng)建DLL打開Visual Studio 2012,,創(chuàng)建如下圖的工程: 輸入工程名字,單擊[OK],; 單擊[Finish],,工程創(chuàng)建完畢了。 現(xiàn)在,,我們就可以在工程中加入我們的代碼了,。加入MyCode.h和MyCode.cpp兩個文件;在MyCode.h中輸入以下代碼: 在MyCode.cpp中輸入以下代碼: 編譯工程,,就會生成DLLDemo1.dll文件,。在代碼中,,很多細(xì)節(jié)的地方,我稍后進(jìn)行詳細(xì)的講解(工程下載),。 使用DLL當(dāng)我們的程序需要使用DLL時,,就需要去加載DLL,在程序中加載DLL有兩種方法,,分別為加載時動態(tài)鏈接和運行時動態(tài)鏈接,。
在實際編程時有兩種使用DLL的方法,,那么到底應(yīng)該使用那一種呢,?在實際開發(fā)時,是基于以下幾點進(jìn)行考慮的:
下面,我將分別使用兩種方法調(diào)用DLL動態(tài)鏈接庫,。 加載時動態(tài)鏈接: 運行時動態(tài)鏈接: 上述代碼都在DLLDemo1工程中,。(工程下載)。 DllMain函數(shù)Windows在加載DLL時,,需要一個入口函數(shù),,就像控制臺程序需要main函數(shù)一樣。有的時候,DLL并沒有提供DllMain函數(shù),,應(yīng)用程序也能成功引用DLL,,這是因為Windows在找不到DllMain的時候,系統(tǒng)會從其它運行庫中引入一個不做任何操作的默認(rèn)DllMain函數(shù)版本,,并不意味著DLL可以拋棄DllMain函數(shù),。 根據(jù)編寫規(guī)范,Windows必須查找并執(zhí)行DLL里的DllMain函數(shù)作為加載DLL的依據(jù),,它使得DLL得以保留在內(nèi)存里,。這個函數(shù)并不屬于導(dǎo)出函數(shù),而是DLL的內(nèi)部函數(shù),,這就說明不能在客戶端直接調(diào)用DllMain函數(shù),,DllMain函數(shù)是自動被調(diào)用的。 DllMain函數(shù)在DLL被加載和卸載時被調(diào)用,,在單個線程啟動和終止時,,DllMain函數(shù)也被調(diào)用。參數(shù)ul_reason_for_call指明了調(diào)用DllMain的原因,,有以下四種情況: DLL_PROCESS_ATTACH:當(dāng)一個DLL被首次載入進(jìn)程地址空間時,,系統(tǒng)會調(diào)用該DLL的DllMain函數(shù),傳遞的ul_reason_for_call參數(shù)值為DLL_PROCESS_ATTACH,。這種情況只有首次映射DLL時才發(fā)生,; DLL_THREAD_ATTACH:該通知告訴所有的DLL執(zhí)行線程的初始化。當(dāng)進(jìn)程創(chuàng)建一個新的線程時,,系統(tǒng)會查看進(jìn)程地址空間中所有的DLL文件映射,,之后用DLL_THREAD_ATTACH來調(diào)用DLL中的DllMain函數(shù)。要注意的是,,系統(tǒng)不會為進(jìn)程的主線程使用值DLL_THREAD_ATTACH來調(diào)用DLL中的DllMain函數(shù),; DLL_PROCESS_DETACH:當(dāng)DLL從進(jìn)程的地址空間解除映射時,,參數(shù)ul_reason_for_call參數(shù)值為DLL_PROCESS_DETACH,。當(dāng)DLL處理DLL_PROCESS_DETACH時,DLL應(yīng)該處理與進(jìn)程相關(guān)的清理操作,。如果進(jìn)程的終結(jié)是因為系統(tǒng)中有某個線程調(diào)用了TerminateProcess來終結(jié)的,,那么系統(tǒng)就不會用DLL_PROCESS_DETACH來調(diào)用DLL中的DllMain函數(shù)來執(zhí)行進(jìn)程的清理工作。這樣就會造成數(shù)據(jù)丟失,; DLL_THREAD_DETACH:該通知告訴所有的DLL執(zhí)行線程的清理工作,。注意的是如果線程的終結(jié)是使用TerminateThread來完成的,那么系統(tǒng)將不會使用值DLL_THREAD_DETACH來執(zhí)行線程的清理工作,,這也就是說可能會造成數(shù)據(jù)丟失,,所以不要使用TerminateThread來終結(jié)線程。以上所有講解在工程DLLMainDemo(工程下載)都有體現(xiàn),。 函數(shù)導(dǎo)出方式在DLL的創(chuàng)建過程中,,我使用的是_declspec( dllexport )方式導(dǎo)出函數(shù)的,,其實還有另一種導(dǎo)出函數(shù)的方式,那就是使用導(dǎo)出文件(.def),。你可以在DLL工程中,,添加一個Module-Definition File(.def)文件。.def文件為鏈接器提供了有關(guān)被鏈接器程序的導(dǎo)出,、屬性及其它方面的信息,。 對于上面的例子,.def可以是這樣的: Module-Definition File(.def)文件的格式如下:
使用def文件,,生成了DLL,,客戶端調(diào)用代碼如下: 可以看到,在調(diào)用GetProcAddress函數(shù)時,,傳入的第二個參數(shù)是MAKEINTRESOURCE(1),,這里面的1就是def文件中對應(yīng)函數(shù)的序號。(工程下載) extern “C”為什么要使用extern “C”呢,?C++之父在設(shè)計C++時,,考慮到當(dāng)時已經(jīng)存在了大量的C代碼,為了支持原來的C代碼和已經(jīng)寫好的C庫,,需要在C++中盡可能的支持C,,而extern “C”就是其中的一個策略。在聲明函數(shù)時,,注意到我也使用了extern “C”,,這里要詳細(xì)的說說extern “C”。 extern “C”包含兩層含義,,首先是它修飾的目標(biāo)是”extern”的,;其次,被它修飾的目標(biāo)才是”C”的,。先來說說extern,;在C/C++中,extern用來表明函數(shù)和變量作用范圍(可見性)的關(guān)鍵字,,這個關(guān)鍵字告訴編譯器,,它申明的函數(shù)和變量可以在本模塊或其它模塊中使用。extern的作用總結(jié)起來就是以下幾點:
接著說”C”的含義。我們都知道C++通過函數(shù)參數(shù)的不同類型支持重載機(jī)制,,編譯器根據(jù)參數(shù)為每個重載函數(shù)產(chǎn)生不同的內(nèi)部標(biāo)識符;但是,,如果遇到了C++程序要調(diào)用已經(jīng)被編譯后的C函數(shù),,那該怎么辦呢?比如上面的int Add ( int a , int b )函數(shù)。該函數(shù)被C編譯器后在庫中的名字為_Add,,而C++編譯器則會生成像_Add_int_int之類的名字用來支持函數(shù)重載和類型安全,。由于編譯后的名字不同,C++程序不能直接調(diào)用C函數(shù),,所以C++提供了一個C連接交換指定符號extern “C”來解決這個問題,;所以,在上面的DLL中,,Add函數(shù)的聲明格式為:extern “C” EXPORTS_DEMO int Add (int a , int b),。這樣就告訴了C++編譯器,函數(shù)Add是個C連接的函數(shù),,應(yīng)該到庫中找名字_Add,,而不是找_Add_int_int。當(dāng)我們將上面DLL中的”C”去掉,,編譯生成新的DLL,,使用Dependency Walker工具查看該DLL,如圖: 請注意導(dǎo)出方式為C++,,而且導(dǎo)出的Add函數(shù)的名字添加了很多的東西,,當(dāng)使用這種方式導(dǎo)出時,客戶端調(diào)用時,,代碼就是下面這樣: 請注意GetProcAddress函數(shù)的第二個參數(shù),,該參數(shù)名就是導(dǎo)出的函數(shù)名,在編碼時,,寫這樣一個名字是不是很奇怪啊,。當(dāng)我們使用extern “C”方式導(dǎo)出時,截圖如下: 注意導(dǎo)出方式為C,,而且函數(shù)名現(xiàn)在就是普通的Add了,。我們再使用GetProcAddress時,就可以直接指定Add了,,而不用再加那一長串奇怪的名字了,。 DLL導(dǎo)出變量DLL定義的全局變量可以被調(diào)用進(jìn)程訪問;DLL也可以訪問調(diào)用進(jìn)程的全局?jǐn)?shù)據(jù),。(工程下載) DLL導(dǎo)出類DLL中定義的類,,也可以被導(dǎo)出。詳細(xì)工程代碼,,請參見(工程下載) 總結(jié)對DLL的講解就到此結(jié)束,,由于MFC在現(xiàn)在的環(huán)境下使用較少,,此處不予講解,如果以后做項目遇到了MFC的DLL相關(guān)知識,我再做總結(jié)。最后,希望大家給我的博客提出一些中肯的建議。 2013年11月30日 于大連,東軟,。 |
|