版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)
文檔簡介
1、介紹、本文意在講解靜態(tài)鏈接庫與動態(tài)鏈接庫的創(chuàng)建與使用,在此之前先來對二者的概念、區(qū)別及優(yōu)缺點進(jìn)行簡要的闡述。其中大多內(nèi)容參考相關(guān)網(wǎng)絡(luò)資料,由于本人能力有限,不能確保完全準(zhǔn)確無誤,若有偏差之處請不吝指出。文中使用到的代碼均在Visual Studio 2008中編譯通過,如果您使用的IDE與本文不同,可根據(jù)實際情況進(jìn)行相應(yīng)項目創(chuàng)建與操作。希望本 文內(nèi)容對您有所幫助。二、概念定義1. 分別編譯與鏈接大多數(shù)高級語言都支持分別編譯(Compiling ),程序員可以顯式地把程序劃分為獨立的模 塊或文件,然后由編譯器(Compiler )對每個獨立部分分別進(jìn)行編譯。在編譯之后,由鏈接 器(Linker)
2、把這些獨立編譯單元鏈接(Linking )到一起。鏈接方式分為兩種:(1)靜態(tài)鏈接方式:在程序開發(fā)中,將各種目標(biāo)模塊(.OBJ )文件、運行時庫 (丄IB )文件,以及經(jīng)常是已編譯的資源(.RES)文件鏈接在一起,以便創(chuàng)建Windows 的.EXE 文件。(2)動態(tài)鏈接方式:在程序運行時,Win dows把一個模塊中的函數(shù)調(diào)用鏈接到庫模塊中的實際函數(shù)上的過程。2. 靜態(tài)鏈接庫與動態(tài)鏈接庫靜態(tài)鏈接庫 (Static Library,簡稱LIB)與動態(tài)鏈接庫(Dynamic Link Library ,簡稱 DLL) 都是共享代碼的方式。如果使用靜態(tài)鏈接庫(也稱靜態(tài)庫),則無論你愿不愿意,丄IB文
3、件中的指令都會被直接包含到最終生成的.EXE文件中。但是若使用.DLL文件,該.DLL文件中的代碼不必被包含在最終的.EXE文件中,.EXE文件執(zhí)行時可以“動態(tài)”地載入和卸載這個 與.EXE文件獨立的.DLL文件。2.1. 動態(tài)鏈接方式鏈接一個DLL有兩種方式:2.1.1 載入時動態(tài)鏈接(Load-Time Dyn amic Lin ki ng)使用載入時動態(tài)鏈接,調(diào)用模塊可以像調(diào)用本模塊中的函數(shù)一樣直接使用導(dǎo)出函數(shù)名調(diào)用DLL中的函數(shù)。這需要在鏈接時將函數(shù)所在 DLL的導(dǎo)入庫鏈接到可執(zhí)行文件中,導(dǎo)入庫向 系統(tǒng)提供了載入 DLL時所需的信息及用于定位 DLL函數(shù)的地址符號。(相當(dāng)于注冊,當(dāng)作
4、API函數(shù)來使用,其實 API函數(shù)就存放在系統(tǒng) DLL當(dāng)中。)2.1.2 運行時動態(tài)鏈接( Run- Time Dy namic Li nking)使用運行時動態(tài)鏈接,運行時可以通過LoadLibrary或LoadLibraryEx函數(shù)載入 DLL。DLL載入后,模塊可以通過調(diào)用GetProcAddress獲取DLL函數(shù)的入口地址,然后就可以通過返回的函數(shù)指針調(diào)用 DLL中的函數(shù)了。如此即可避免導(dǎo)入庫文件了。2.2. 二者優(yōu)點及不足2.2.1 靜態(tài)鏈接庫的優(yōu)點(1) 代碼裝載速度快,執(zhí)行速度略比動態(tài)鏈接庫快;(2) 只需保證在開發(fā)者的計算機中有正確的丄IB文件,在以二進(jìn)制形式發(fā)布程 序時不需考
5、慮在用戶的計算機上丄IB文件是否存在及版本冋題,可避免DLL地 獄等問題。2.2.2 動態(tài)鏈接庫的優(yōu)點(1) 更加節(jié)省內(nèi)存并減少頁面交換; DLL文件與EXE文件獨立,只要輸出接口不變(即名稱、參數(shù)、返回值類 型和調(diào)用約定不變),更換DLL文件不會對EXE文件造成任何影響,因而極大 地提高了可維護(hù)性和可擴展性;(3) 不同編程語言編寫的程序只要按照函數(shù)調(diào)用約定就可以調(diào)用同一個DLL函數(shù)。2.2.3 不足之處(1) 使用靜態(tài)鏈接生成的可執(zhí)行文件體積較大,包含相同的公共代碼,造成浪費;(2) 使用動態(tài)鏈接庫的應(yīng)用程序不是自完備的,它依賴的DLL模塊也要存在,如果使用載入時動態(tài)鏈接,程序啟動時發(fā)現(xiàn)D
6、LL不存在,系統(tǒng)將終止程序并給出錯誤信息。而使用運行時動態(tài)鏈接,系統(tǒng)不會終止,但由于DLL中的導(dǎo)出函數(shù)不可用,程序會加載失?。?3) 使用動態(tài)鏈接庫可能造成DLL地獄。2.3. DLL 地獄DLL地獄(DLL Hell )是指因為系統(tǒng)文件被覆蓋而讓整個系統(tǒng)像是掉進(jìn)了地獄。簡單地講,DLL地獄是指當(dāng)多個應(yīng)用程序試圖共享一個公用組件時,如某個DLL或某個組件對象模型(COM)類,所引發(fā)的一系列問題。最典型的情況是,某個應(yīng)用程序?qū)⒁惭b一個新版本的共享組件,而該組件與機器上的現(xiàn)有版本不向后兼容。雖然剛安裝的應(yīng)用程序運行正常,但原來依賴前一版本共享組件的應(yīng)用程序也許已無法再工作。在某些情況下,問題的起
7、因更加難以預(yù)料。比如,當(dāng)用戶瀏覽某些 web站點時會同時下載某個 Microsoft ActiveX控件。如果下載該控件,它將替換機器上原 有的任何版本的控件。如果機器上的某個應(yīng)用程序恰好使用該控件,則很可能也會停止工作。在許多情況下,用戶需要很長時間才會發(fā)現(xiàn)應(yīng)用程序已停止工作。結(jié)果往往很難記起是何時的機器變化影響到了該應(yīng)用程序。這些問題的原因是應(yīng)用程序不同組件的版本信息沒有由系統(tǒng)記錄或加強。而且,系統(tǒng)為某個應(yīng)用程序所做的改變會影響機器上的所有應(yīng)用程序一現(xiàn)在建立完全從變化中隔離出來的應(yīng)用程序并不容易。三、靜態(tài)鏈接庫的創(chuàng)建與使用在此通過一個實例來介紹靜態(tài)庫的創(chuàng)建與使用。在該實例中,我們將一個實現(xiàn)
8、兩整數(shù)相加求和的函數(shù)封裝到靜態(tài)庫中供其他程序調(diào)用。1.創(chuàng)建首先,使用Visual Studio 2008 來創(chuàng)建一個帶預(yù)編譯頭的靜態(tài)庫項目Static,該項目包含在名為Library的解決方案中。1.1. 創(chuàng)建靜態(tài)庫項目創(chuàng)建一個不帶預(yù)編譯頭的靜態(tài)鏈接庫項目有以下幾個步驟:(1) 單擊菜單命令“文件”-“新建”-“項目”,彈出“新建項目”對話框;(2) 在彈出的“新建項目”對話框中,選擇左邊“類別”列表中選擇“ VisualC+ ” -“ Win32 ”,在右邊的“模版”中選擇“ Win32項目”;(3) 在下方輸入項目名稱“ Static ”,并選擇項目創(chuàng)建的位置,勾選“生成解 決方案”,并輸
9、入解決方案名稱“ Library ”,然后點擊“確定”按鈕;(4) 點擊兩次“下一步”按鈕,進(jìn)入“應(yīng)用程序設(shè)置”界面;(5) 在“應(yīng)用程序設(shè)置”界面中,選擇“靜態(tài)庫”,并確保下方“附加選項” 中的“預(yù)編譯頭”被勾選,然后點擊“完成”按鈕。12 編輯項目經(jīng)過上面的步驟,初步創(chuàng)建了一個帶預(yù)編譯頭的靜態(tài)庫項目,接下來編輯該項目以達(dá)到我們的創(chuàng)建靜態(tài)庫的目的。首先添加一個用于定義導(dǎo)出函數(shù)的源文件Static.cpp,編碼實現(xiàn)兩個整數(shù)相加的Add函數(shù)。源文件代碼如下:#include“ StdAfx.h/ 標(biāo)準(zhǔn)頭文件int Add(i nt a, i nt b)return a + b;接著點擊菜單命令,
10、“工具”-“生成Static ”。如果一切順利的話,就會在解決方案的“ Debug 目錄中生成了名為“ Static.lib ”的靜態(tài)鏈接庫。同時,需要給該靜態(tài)鏈接庫編寫一個聲明頭文件Static.h,以便在鏈接時告知編譯該鏈接庫中的導(dǎo)出函數(shù)聲明。Static.h中的代碼很簡單,只要聲明一下 Add函數(shù)就可以:#ifndef _STATIC H/防止該頭文件重復(fù)引用#defi ne _STATIC_H_int Add( int a, i nt b);/聲明導(dǎo)出函數(shù)#en dif接著點擊菜單命令,“工具”-“生成Static ”。如果一切順利的話,就會在Library解決方案的Debug目錄中生
11、成了名為 MyDLL.lib的靜態(tài)鏈接庫。2.使用在Library解決方案下,再添加一個Win32控制臺應(yīng)用程序空項目UseLIB。程序主文件名為UseLIB.cpp,其中包含用于調(diào)用Add函數(shù)的程序入口函數(shù)main。將剛才創(chuàng)建的 Static.lib及其聲明頭文件 Static.h 一同復(fù)制到UseLIB項目目錄下。并在源文件UseLIB.cpp中使用預(yù)Static.h編譯命令鏈接Static.lib (也可以在IDE的項目屬性中設(shè)置鏈接器選項,或者只復(fù)制 文件并設(shè)置UseLIB項目的"項目依賴項”為Static項目)。源文件UseLIB.cpp中的代碼如下:#pragma com
12、ment(lib,“ Static.lib / 鏈接靜態(tài)庫 Static.lib#i nclude <stdio.h>#include“ Static.h II包含Static.lib的聲明頭文件,聲明導(dǎo)出函數(shù)Addint main( void)int a = 1, b = 2;printf( “ d+%d=%d” , a, b, Add(a, b); II 調(diào)用 Static.lib 中的 Add 函數(shù)return 0;接下來點擊菜單命令,“工具”-“生成UseLIB ”。如果順利的話,就會在 Library解決方案的Debug目錄中生成了名為 UseLIB.exe的可執(zhí)行執(zhí)文件
13、, 運行UseLIB.exe,將在控制臺 中輸出結(jié)果:1+2=33.注意由于項目中創(chuàng)建的源文件為.CPP文件,即C+源文件,因此 Visual C+按C+規(guī)范,并采用_cdecl調(diào)用約定對其進(jìn)行編譯。這樣得到的導(dǎo)出函數(shù)就不能被C語言程序所調(diào)用。解決該問題的辦法是在函數(shù)體名稱前添加extern “修飾,告訴編譯器,該函數(shù)按照C語言規(guī)范,并采用_cdecl調(diào)用約定進(jìn)行編譯。因此源文件Add.cpp中的代碼可修改如下:extern“ C” int add(int a, int b)return a + b;最后重新編譯該靜態(tài)鏈接庫項目,導(dǎo)出函數(shù)Add就能夠被C語言程序所調(diào)用了。另一種不改變代碼的方法
14、是在“Static屬性頁”左邊的列表中選擇“配置屬性”-“ C/C+ ”-“高級”,然后在右邊的“調(diào)用約定”選擇“_cdecl (/Gd) ”,“編譯為”選擇“編譯為C+代碼(ITP) ”。這種方法在不同的IDE上設(shè)置方法有所不同。四、動態(tài)鏈接庫的創(chuàng)建與使用在此同樣通過一個實例來介紹動態(tài)鏈接庫的創(chuàng)建與使用。在實例中,依然使用Add函數(shù)進(jìn)行講解,這樣一方面可以沿用上面靜態(tài)鏈接的有關(guān)內(nèi)容,另一方面也可以了解動態(tài)鏈接庫與靜態(tài)鏈接庫在創(chuàng)建和使用上的異同。1.創(chuàng)建首先,在之前創(chuàng)建的 Library解決方案中添加一個帶預(yù)編譯頭的動態(tài)鏈接庫項目,項目名稱 為Dynamic。使用不同IDE的朋友可以根據(jù)實際情
15、況進(jìn)行創(chuàng)建。1.1. 創(chuàng)建動態(tài)鏈接庫項目創(chuàng)建一個帶預(yù)編譯頭的動態(tài)鏈接庫項目有以下幾個步驟:(1)單擊菜單命令“文件”-“新建”-“項目”,彈出“新建項目”對話框; 在彈出的“新建項目”對話框中,選擇左邊列表的“Visual C+ ”-“ Win32 ” 在右邊的項目模版中選擇“ Win32項目”; 在下方輸入項目名稱“Dynamic ” ,并選擇項目創(chuàng)建的位置,然后點擊“確 定”按鈕;(4) 點擊兩次“下一步”按鈕,進(jìn)入“應(yīng)用程序設(shè)置”界面;(5) 在“應(yīng)用程序設(shè)置”界面中,選擇“ DLL”,然后點擊“完成”按鈕。1.2. 編輯項目Dynamic項目自動生成的 dllmain.cpp 源文件含
16、有一個名為DllMain的函數(shù),該函數(shù)是 DLL被鏈接時的入口函數(shù),它由系統(tǒng)自動調(diào)用,在這里我們不用去理會它。與前面創(chuàng)建靜態(tài)態(tài)鏈接庫類似的,首先添加一個用于定義導(dǎo)出函數(shù)的源文件Dynamic.cpp ,編碼實現(xiàn)兩個整數(shù)相加的Add函數(shù)。源文件代碼如下:extern “ C” _declspec(dllexport) int Add(int a, int b) / 聲明為 DLL 導(dǎo)出函數(shù)return a + b;與前面靜態(tài)鏈接庫不同,在Add函數(shù)體名稱前不只添加了extern“(修飾,還多添加了一個_declspec(dllexport) 修飾。_declspec(dllexport) 修飾的
17、作用是告訴編譯器,這個函數(shù)將作為導(dǎo)出函數(shù),并在輸入庫中生成該函數(shù)的地址符號等信息,這樣其他程序就可以使用載入時動態(tài)鏈接方式來調(diào)用該函數(shù)。另外, extern “在封裝DLL還有另一個作用,就是告訴 編譯器,在DLL中的導(dǎo)出函數(shù)不要使用函數(shù)名修飾規(guī)則,這樣在采用運行時動態(tài)鏈接時就 可以直接使用原函數(shù)名來調(diào)用導(dǎo)出函數(shù)了。關(guān)于函數(shù)調(diào)用方式和導(dǎo)出方式的詳細(xì)說明在后面還將提出,現(xiàn)在先撇開這些煩人的問題。接著點擊菜單命令,“工具”-“生成Dynamic "。如果一切順利的話,就會在 Library解決 方案的Debug目錄中生成了名為 Dynamic.dll的動態(tài)鏈接庫和名為 Dynamic.l
18、ib的導(dǎo)入庫(與 靜態(tài)鏈接庫不同,只存放 DLL的導(dǎo)出表,不包含代碼)。最后需要給該 Dynamic.dll的輸入庫Dynamic.lib編寫一個聲明頭文件 Dynamic.h,以便在 以后鏈接時告知編譯器該鏈接庫中的具體的導(dǎo)入內(nèi)容(一般包括代碼和資源)。Dyn amic.h中的代碼很簡單,只要聲明一下 Add函數(shù)就可以:#ifndef _DYNAMIC_H_ /防止該頭文件重復(fù)引用#defi ne _DYNAMIC_H_declspec(dllexport) int Add(i nt a, i nt b);/聲明導(dǎo)出函數(shù)#en dif2.使用在同Library解決方案中,添加一個名為UseD
19、LL的Win32控制臺應(yīng)用程序空項目。程序主文件名為UseDLL.cpp,其中包含用于調(diào)用 Add函數(shù)的程序入口函數(shù) main。一下使用兩種動 態(tài)鏈接方式來鏈接 Dynamic.dll。2.1. 載入時動態(tài)鏈接載入時動態(tài)鏈接是一種輕松使用動態(tài)鏈接庫的方法,它使得使用動態(tài)鏈接庫如同使用靜態(tài)鏈接庫一樣方便。將導(dǎo)入庫Dynamic.lib及其聲明頭文件 Dynamic.h 一同復(fù)制到UseDLL項目目錄下,并把 Dynamic.dll復(fù)制到項目的 Debug目錄中。并在源文件 UseDLL.cpp中使用預(yù) 編譯命令鏈接Dynamic.lib (也可以在IDE的項目屬性中設(shè)置鏈接器選項,或者只復(fù)制Dy
20、namic.h 文件并設(shè)置 UseDLL項目的“項目依賴項"為Dynamic項目)。源文件UseDLL.cpp中的代碼如下:#pragma comme nt(lib.Dynamic.l/b 鏈接導(dǎo)入庫 Dynamic.lib#i nclude <stdio.h>#include“ Dynamic/h包含Dynamic.lib 的聲明頭文件,提供導(dǎo)出函數(shù)Add的聲明int main( void)int a = 1, b = 2;printf( “ d+%d=%rf' , a, b, Add(a, b);/ 調(diào)用 Dynamic.DLL 中的 Add 函數(shù)getcha
21、r();/用于查看輸出結(jié)果return 0;幾乎跟使用靜態(tài)鏈接庫一樣。接下來點擊菜單命令,“工具”-“生成UseDLL”。如果一切順利的話,就會在Library解決方案的Debug目錄中生成了名為 UseDLL.exe的可執(zhí)行文件, 運行UseDLL.exe文件,將在控制臺中輸出結(jié)果:1+2=32.2. 運行時動態(tài)鏈接運行時動態(tài)鏈接的代碼相對麻煩些,需要使用到Windows的三個API函數(shù),還要進(jìn)行一些判斷以防止不必要的麻煩。我們在 UseDLL項目的基礎(chǔ)上做些修改來實現(xiàn)運行時動態(tài)鏈接。 這里只需要把 Dynamic.dll復(fù)制到UseDLL項目的Debug目錄中,因為不用在編譯的時候鏈 接導(dǎo)
22、入庫,只要在運行根據(jù)需要鏈接Dynamic.dll。下面先給出修改后的源文件Dynamic.cpp的代碼:#include <windows.h>/ 用于聲明 window API 函數(shù)及宏等#i nclude <stdio.h>typedef int (* FuncAdd)(int a, int b);/定義將要調(diào)用的導(dǎo)出函數(shù)Add的指針類型int main( void)FuncAdd Add;/ 定義Add函數(shù)指針int a = 1, b = 2;載入DLL,并獲取其句柄HMODULE hDLL = LoadLibrary(TEXT("MyDLL.dll&
23、quot;); /if (hDLL) / MyDLL.dll載入成功Add = (FuncAdd)GetProcAddress(hDLL, "Add");/獲取導(dǎo)出函數(shù) Add 指針if (Add) /正確獲取Add函數(shù)指針prin tf("%d+%d=%dn", a, b, Add(a, b);/調(diào)用導(dǎo)出函數(shù) Addelse/沒有找到Add函數(shù)prin tf("Add Not Foun d!n");else / MyDLL.dll載入失敗prin tf("LoadLibrary Failed!n ”);getchar();
24、FreeLibrary(TEXT("MyDLL.dll"); / 釋放 DLLreturn 0;看到了吧,調(diào)用方法比較繁瑣。由于沒有鏈接導(dǎo)入庫,不能使用地址符號定位導(dǎo)出函數(shù)的入口地址,只能通過 GetProcAdress來獲取其在地址空間中的指針,再通過指針調(diào)用。但程序 在運行之前,GetProcAdress無法判斷指針的有效性。 因此,為了防止Dynamic項目中不存 在Add函數(shù)而使程序在運行時出錯,有必要在調(diào)用Add之前判斷其函數(shù)指針的有效性。最后,點擊菜單命令,“工具”-“重新生成UseDLL”。如果一切順利的話,就會在 Library解決方案的Debug目錄中生成
25、了名為 UseDLL.exe的可執(zhí)行文件,運行 UseDLL.exe文件, 將在控制臺中輸出結(jié)果:1+2=3五、補充閱讀1. Visual Studio 2008 中DLL項目只生成dll文件不生成lib文件的解決方案在創(chuàng)建動態(tài)鏈接庫項目時, 如果在“應(yīng)用程序設(shè)置”中只勾選“預(yù)編譯頭”,而沒有勾選“空項目”或“導(dǎo)出符號”,那么在對該項目進(jìn)行編譯鏈接時將只會生成動態(tài)鏈接庫dll文件,不生成導(dǎo)入庫lib文件。此問題的解決辦法如下:(1)右鍵單擊該項目,在彈出的菜單中點擊“添加” -“新建項”; 在彈出的“添加新項”對話框中,在左邊“類別”列表中選擇“Visual C+”-“代碼”,在右邊“模版”中
26、選擇“模塊定義文件(.def) ”;(3) 在“名稱”輸入框中輸入任意名稱,這里使用該項目名稱,如“MyDLL ”, 單擊“確定”按鈕;(4) 重新生成該項目,即生成lib文件。注意,此時在項目屬性的配置列表 “配置屬性”-“鏈接器”-“輸入”中的“模塊定義文件” 項目中將出現(xiàn)剛才創(chuàng)建的模塊定義文件MyDLL.def。如果此前不是添加“新建項”,而是添加“現(xiàn)有項”,那么必須在此項目上填寫該現(xiàn)有模塊定義文件的文件名,否則將不會生成lib文件。2. 動態(tài)鏈接庫的應(yīng)用舉例(1) 所有的Windows 系統(tǒng)調(diào)用(Windows API函數(shù))都是以動態(tài)鏈接庫的形式提供的。我們在 Windows目錄下的s
27、ystem32 文件夾中會看到 kernel32.dll 、user32.dll 和 gdi32.dll ,windows 的大多數(shù) API 都包含在這 些DLL中。kernel32.dll中的函數(shù)主要處理內(nèi)存管理和進(jìn)程調(diào)度;user32.dll中的函數(shù)主要控制用戶界面;gdi32.dll中的函數(shù)則負(fù)責(zé)圖形方面的操作。與這 些動態(tài)庫相對應(yīng)的導(dǎo)入庫分別為 kernel32.lib 、user32.lib 和gdi32.lib。(2) 軟件的自動更新。Win dows應(yīng)用的開發(fā)者常常利用動態(tài)鏈接庫來分發(fā)軟 件更新。他們生成一個動態(tài)庫的新版本,然后用戶可以下載,并用它替代當(dāng)前 的版本。當(dāng)然,新、舊版
28、本動態(tài)庫的輸出接口 (即導(dǎo)出函數(shù))必須一致。下一次用 戶運行應(yīng)用程序時,應(yīng)用將自動鏈接和加載新的動態(tài)庫。(3) 軟件插件技術(shù)。許多Windows應(yīng)用軟件都支持插件擴展方式,如IE瀏覽 器、Photoshop、Office等等。插件在本質(zhì)上都是動態(tài)庫。 可擴展的Web服務(wù)器。(5) 每個Windows驅(qū)動程序在本質(zhì)上都是動態(tài)鏈接庫。3. _declspec(dllexport)與.def文件在32位編譯器版本中,可以使用_declspec(dllexport)關(guān)鍵字從DLL導(dǎo)出數(shù)據(jù)、函數(shù)、類或類成員函數(shù)。_declspec(dllexport)將導(dǎo)出指令添加到對象文件(即obj文件),若要導(dǎo)出函
29、數(shù),_declspec(dllexport)關(guān)鍵字必須出現(xiàn)在調(diào)用約定關(guān)鍵字的左邊(如果指定了關(guān)鍵字)例如:_declspec(dllexport) void _cdecl Function 1(void);若要導(dǎo)出類中的所有公共數(shù)據(jù)成員和成員函數(shù),關(guān)鍵字必須出現(xiàn)在類名的左邊,如下所示:class _declspec(dllexport) CExampleExport : public CObject . class defi niti on . ;生成DLL時,通常創(chuàng)建一個包含正在導(dǎo)出的函數(shù)原型和/或類的頭文件,并將_declspec(dllexport) 添加到頭文件中的聲明。若要提高代碼的
30、可讀性,請為_declspec(dllexport)定義一個宏并對正在導(dǎo)出的每個符號使用該宏:#defi ne Export _declspec(dllexport)模塊定義文件(.def)是包含一個或多個描述各種DLL屬性的模塊語句的文本文件。二者的目的都是將公共符號導(dǎo)入到應(yīng)用程序中或從DLL導(dǎo)出函數(shù)。添加_declspec(dllexport)是為了提供不使用.def文件從.EXE或.DLL導(dǎo)出函數(shù)的簡單方法。如果不使用_declspec(dllimport)或_declspec(dllexport) 導(dǎo)出 DLL 函數(shù),貝U DLL 需要.def 文件。并不是任何時候選擇添加 _decl
31、spec(dllexport)而放棄.def的方式都是好的。如果DLL是提供給VC+用戶使用的,只需要把編譯DLL時產(chǎn)生的.lib提供給用戶,它可以很輕松地調(diào)用你的DLL。但是如果DLL是供VB、PB、Delphi用戶使用的,那么會產(chǎn)生一個小麻煩。因為 VC+對于_declspec(dllexport)聲明的函數(shù)會進(jìn)行名稱轉(zhuǎn)換,比如函數(shù):_declspec(dllexport) int _stdcall IsWi nN T()會轉(zhuǎn)換為IsWinNT0,這樣你在VB中必須這樣聲明:Declare Function IsWinNT Lib "my.dll" Alias &quo
32、t;IsWinNT0" () As Long的后面的數(shù)由于參數(shù)類型不同而可能不同。這顯然不太方便。所以如果要想避免這種轉(zhuǎn)換,就要使用.def文件方式。4.函數(shù)調(diào)用約定(Calling Convention)函數(shù)調(diào)用約定不僅決定了發(fā)生函數(shù)調(diào)用時函數(shù)參數(shù)的入棧順序,還決定了是由調(diào)用者函數(shù)還是被調(diào)用函數(shù)負(fù)責(zé)清除棧中的參數(shù),還原堆棧。函數(shù)調(diào)用約定有很多方式,除了常見的_cdecl , _fastcall和_stdcall之外,C+的編譯器還支持 thiscall方式,不少 C/C+編譯 器還支持naked call方式。這么多函數(shù)調(diào)用約定常常令許多程序員很迷惑,到底它們是怎么回事,都是在什么
33、情況下使用呢?下面就分別介紹這幾種函數(shù)調(diào)用約定。4.1. 各種調(diào)用約定4.1.1cdecl編譯器的命令行參數(shù)是/Gd , _cdecl是C Declaration的縮寫,是C和C+程序的缺省調(diào)用 方式。所有參數(shù)從右到左依次入棧,這些參數(shù)由調(diào)用者清除,每一個調(diào)用它的函數(shù)都包含清空堆棧的代碼,稱為手動清棧。所以產(chǎn)生的可執(zhí)行文件大小會比調(diào)用_stdcall函數(shù)的大。被調(diào)用函數(shù)無需要求調(diào)用者傳遞多少參數(shù),調(diào)用者傳遞過多或者過少的參數(shù),甚至完全不同的參數(shù)都不會產(chǎn)生編譯階段的錯誤。所有非C+成員函數(shù)和那些沒有用 _stdcall或_fastcall聲明的函數(shù)都默認(rèn)是 _cdecl方式,它使用 C函數(shù)調(diào)用方
34、式。4.1.2 _stdcall_stdcall是Standard Call的縮寫,是Pascal程序的缺省調(diào)用方式,通常用于 Win32 API中,是WIN32 API的標(biāo)準(zhǔn)調(diào)用方式:所有參數(shù)從右到左依次入棧,如果是調(diào)用類成員的話,最 后一個入棧的是this指針。這些堆棧中的參數(shù)由被調(diào)用的函數(shù)在返回后清除,使用的指令 是retn X,X表示參數(shù)占用的字節(jié)數(shù),CPU在ret之后自動彈出X個字節(jié)的堆??臻g。稱為自動清棧。函數(shù)在編譯的時候就必須確定參數(shù)個數(shù),并且調(diào)用者必須嚴(yán)格的控制參數(shù)的生成,不能多,不能少,否則返回后會出錯。4.1.3 _fastcall_fastcall是編譯器指定的快速調(diào)用方
35、式。由于大多數(shù)的函數(shù)參數(shù)個數(shù)很少,使用堆棧傳遞 比較費時。因此_fastcall通常規(guī)定將前兩個(或若干個)參數(shù)由寄存器傳遞,其余參數(shù)還 是通過堆棧傳遞。不同編譯器編譯的程序規(guī)定的寄存器不同。返回方式和stdcall相當(dāng)。4.1.4 _thiscall_thiscall是為了解決類成員調(diào)用中 this指針傳遞而規(guī)定的。_thiscall要求把this指針放在 特定寄存器中,該寄存器由編譯器決定。VC使用ecx,Borland的C+編譯器使用eax。返回方式和_stdcall相當(dāng)。4.1.5 naked call采用前面幾種函數(shù)調(diào)用約定的函數(shù),編譯器會在必要的時候自動在函數(shù)開始添加保存ESI,E
36、DI,EBX, EBP寄存器的代碼,在退出函數(shù)時恢復(fù)這些寄存器的內(nèi)容,使用naked call方式聲明的函數(shù)不會添加這樣的代碼,這也就是為什么稱其為naked的原因吧。naked call不是類型修飾符,故必須和 _declspec共同使用。4.2. 綜述_fastcall和_thiscall涉及的寄存器由編譯器決定,因此不能用作跨編譯器的接口。所以Windows上的COM對象接口都定義為 _stdcall調(diào)用方式。帶有可變參數(shù)的函數(shù)必須且只能使用_cdecl方式,例如下面的函數(shù):in t prin tf(char * fmtStr,);int sca nf(char * fmtStr, .)
37、;_stdcall和_cdecl這兩個關(guān)鍵字看起來似乎很少和我們打交道,但是看了下面的定義(來 自windef.h ),你一定會覺得驚訝:#define CALLBACK _stdcall#defi ne WINAPI_stdcall#define WINAPIV_cdecl#defi ne APIENTRY WINAPI#defi ne APIPRIVATE _stdcall#define PASCAL_stdcall#defi ne cdecl _cdecl#ifndef CDECL#defi ne CDECL _cdecl#en dif幾乎我們寫的每一個 WINDOWS API函數(shù)都是_
38、stdcall類型的,為什么?首先,我們談一下兩者之間的區(qū)別:WINDOWS的函數(shù)調(diào)用時需要用到棧。當(dāng)函數(shù)調(diào)用完成后,棧需要清除,這里就是問題的關(guān)鍵,如何清除?如果我們的函數(shù)使用了 _cdecl,那么棧的清除工作是由調(diào)用者,用COM的術(shù)語來講就是客戶來完成的。這樣帶來了一個棘手的問題,不同的編譯器產(chǎn)生棧的方式不盡相同,那么調(diào)用者能否正常的完成清除工作呢?答案是不能。如果使用_stdcall,上面的問題就解決了,函數(shù)自己解決清除工作。所以,在跨開發(fā)平 臺的調(diào)用中,我們都使用 _stdcall (雖然有時是以 WINAPI的樣子出現(xiàn)),女口 JNI。那么為什么還需要_cdecl呢?當(dāng)我們遇到這樣的
39、函數(shù)如fprintf()它的參數(shù)是可變的,不定長的,被調(diào)用者事先無法知道參數(shù)的長度(如typedef in t (*MYPROC)(LPTSTR,);),事后的清除工作也無法正常的進(jìn)行,因此,這種情況我們只能使用_cdecl。到這里我們有一個結(jié)論,如果你的程序中沒有涉及可變參數(shù),最好使用_stdcall關(guān)鍵字。5.函數(shù)名字修飾(Decorated Name )規(guī)則函數(shù)的名字修飾(Decorated Name )就是編譯器在編譯期間創(chuàng)建的一個字符串,用來指明 函數(shù)的定義或原型。LINK程序或其他工具有時需要指定函數(shù)的名字修飾來定位函數(shù)的正確 位置。多數(shù)情況下程序員并不需要知道函數(shù)的名字修飾,LI
40、NK程序或其他工具會自動區(qū)分他們。當(dāng)然,在某些情況下需要指定函數(shù)的名字修飾,例如在C+程序中,為了讓LINK程序或其他工具能夠匹配到正確的函數(shù)名字,就必須為重載函數(shù)和一些特殊的函數(shù)(如構(gòu)造函數(shù)和析構(gòu)函數(shù))指定名字裝飾。另一種需要指定函數(shù)的名字修飾的情況是在匯編程序中調(diào)用 C或C+的函數(shù)。如果函數(shù)名字,調(diào)用約定,返回值類型或函數(shù)參數(shù)有任何改變,原來的 名字修飾就不再有效,必須指定新的名字修飾。C和C+程序的函數(shù)在內(nèi)部使用不同的名字修飾方式,下面將分別介紹這兩種方式。5.1. C編譯器的函數(shù)名修飾規(guī)則對于_stdcall調(diào)用約定,編譯器和鏈接器會在輸出函數(shù)名前加上一個下劃線前綴,函數(shù)名后面加上一個
41、"”符號和其參數(shù)的字節(jié)數(shù),例如 _functionnamenumber 。_cdecl調(diào)用 約定僅在輸出函數(shù)名前加上一個下劃線前綴,例如_functionname 。_fastcall調(diào)用約定在輸出函數(shù)名前加上一個“ ”符號,后面也是一個 “符號和其參數(shù)的字節(jié)數(shù),例如fun cti onn ame nu mber 。52C+編譯器的函數(shù)名修飾規(guī)則C+的函數(shù)名修飾規(guī)則有些復(fù)雜,但是信息更充分,通過分析修飾名不僅能夠知道函數(shù)的調(diào)用方式,返回值類型,參數(shù)個數(shù)甚至參數(shù)類型。不管_cdecl,_fastcall還是_stdcall調(diào)用方式,函數(shù)修飾都是以一個“?”開始,后面緊跟函數(shù)的名字,再后
42、面是參數(shù)表的開始標(biāo)識和按照參數(shù)類型代號拼出的參數(shù)表。對于_stdcall方式,參數(shù)表的開始標(biāo)識是“YG”,對于_cdecl方式則是“ YA”,對于_fastcall方式則是“ YI”。參數(shù)表的拼寫代號 如下所示:代號 類型XvoidDcharEun sig nedcharFshortHintIun sig nedintJlongKun sig nedlongMfloatNdouble_NboolOlong doublePA指針前綴AA引用前綴V類名 類指針的方式有些特別,用PA表示指針,用PB表示const類型的指針,如果是引用,則在類型代號前加上 AA。后面的代號表明指針類型,如果相同類型的指針連續(xù)出現(xiàn),以“0 ”代替,一個“ 0”代表一次重復(fù)。如果相同類型的引用連續(xù)出現(xiàn),則以“1”代替,每個“ 1 ”都代表一次重復(fù)。U表示結(jié)構(gòu)類型,通常后跟結(jié)構(gòu)體的類型名,用“”表示結(jié)構(gòu)類型名的結(jié)束。函數(shù)的返回值不作特殊處理,它的描述方式和函數(shù)參數(shù)一樣,緊跟著參數(shù)表的開始標(biāo)志,也就是說,函數(shù)參數(shù)表的第一項實際上是表示函數(shù)的返回值類型。參數(shù)表后以“Z”標(biāo)識整個名字的結(jié)束,如果該函數(shù)無參數(shù),則以“Z”標(biāo)識結(jié)束。下面舉三個個例子。函數(shù)聲明:int Function 1(char *var1, un sig ned Ion g);函數(shù)修飾:?Fu nctio n1YGHPADKZ函數(shù)聲明:void
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 臨時工招募:2024學(xué)校勤雜工勞動協(xié)議
- 2025年數(shù)據(jù)中心場承包運營管理協(xié)議4篇
- 專項龍門吊租賃協(xié)議格式范本2024版B版
- 2025年度健身中心場地租賃及私人教練服務(wù)合同4篇
- 二零二四商鋪租賃合同(含租賃期間租賃物處置及變現(xiàn)條款)3篇
- 2025不銹鋼精密鑄造件加工與銷售合作協(xié)議2篇
- 2025年度文化衍生品研發(fā)、生產(chǎn)及銷售合作協(xié)議4篇
- 二零二五年度餐飲業(yè)人力資源外包合同6篇
- 2024藥店負(fù)責(zé)人任期藥店經(jīng)營數(shù)據(jù)統(tǒng)計與分析聘用合同3篇
- 一次性付款房地產(chǎn)轉(zhuǎn)讓合同(2024年版)
- NGS二代測序培訓(xùn)
- 《材料合成與制備技術(shù)》課程教學(xué)大綱(材料化學(xué)專業(yè))
- 小紅書食用農(nóng)產(chǎn)品承諾書示例
- 釘釘OA辦公系統(tǒng)操作流程培訓(xùn)
- 新生兒科年度護(hù)理質(zhì)控總結(jié)
- GB/T 15934-2024電器附件電線組件和互連電線組件
- 《工貿(mào)企業(yè)有限空間作業(yè)安全規(guī)定》知識培訓(xùn)
- 高層次人才座談會發(fā)言稿
- 垃圾清運公司管理制度(人員、車輛、質(zhì)量監(jiān)督、會計管理制度)
- 《建筑工程設(shè)計文件編制深度規(guī)定》(2022年版)
- 營銷人員薪酬考核方案
評論
0/150
提交評論