DLL 的导入与导出
動(dòng)態(tài)鏈接庫(kù)(DLL)是從C語(yǔ)言函數(shù)庫(kù)和Pascal庫(kù)單元的概念發(fā)展而來(lái)的。所有的C語(yǔ)言標(biāo)準(zhǔn)庫(kù)函數(shù)都存放在某一函數(shù)庫(kù)中。在鏈接應(yīng)用程序的過(guò)程中,鏈接器從庫(kù)文件中拷貝程序調(diào)用的函數(shù)代碼,并把這些函數(shù)代碼添加到可執(zhí)行文件中。這種方法同只把函數(shù)儲(chǔ)存在已編譯的OBJ文件中相比更有利于代碼的重用。但隨著Windows這樣的多任務(wù)環(huán)境的出現(xiàn),函數(shù)庫(kù)的方法顯得過(guò)于累贅。如果為了完成屏幕輸出、消息處理、內(nèi)存管理、對(duì)話框等操作,每個(gè)程序都不得不擁有自己的函數(shù),那么Windows程序?qū)⒆兊梅浅}嫶?。Windows的發(fā)展要求允許同時(shí)運(yùn)行的幾個(gè)程序共享一組函數(shù)的單一拷貝。動(dòng)態(tài)鏈接庫(kù)就是在這種情況下出現(xiàn)的。動(dòng)態(tài)鏈接庫(kù)不用重復(fù)編譯或鏈接,一旦裝入內(nèi)存,DLL函數(shù)可以被系統(tǒng)中的任何正在運(yùn)行的應(yīng)用程序軟件所使用,而不必再將DLL函數(shù)的另一拷貝裝入內(nèi)存。
下面我們一步一步來(lái)建立一個(gè)DLL。
一、建立一個(gè)DLL工程
新建一個(gè)工程,選擇Win32 控制臺(tái)項(xiàng)目(Win32 Console Application),并且在應(yīng)用程序設(shè)置標(biāo)簽(the advanced tab)上,選擇DLL和空項(xiàng)目選項(xiàng)。
二、聲明導(dǎo)出函數(shù)
這里有兩種方法聲明導(dǎo)出函數(shù):一種是通過(guò)使用__declspec(dllexport),添加到需要導(dǎo)出的函數(shù)前,進(jìn)行聲明;另外一種就是通過(guò)模塊定義文件(Module-Definition File即.DEF)來(lái)進(jìn)行聲明。
第一種方法,建立頭文件DLLSample.h,在頭文件中,對(duì)需要導(dǎo)出的函數(shù)進(jìn)行聲明。
#define?_DLL_SAMPLE_H
//?如果定義了C++編譯器,那么聲明為C鏈接方式
#ifdef __cplusplus
extern?"C"?{
#endif
?
//?通過(guò)宏來(lái)控制是導(dǎo)入還是導(dǎo)出
?#ifdef _DLL_SAMPLE
#define?DLL_SAMPLE_API __declspec(dllexport)
?#else
?#define?DLL_SAMPLE_API __declspec(dllimport)
?#endif
?
//?導(dǎo)出/導(dǎo)入函數(shù)聲明
?DLL_SAMPLE_API?void?TestDLL(int);
#undef?DLL_SAMPLE_API
#ifdef __cplusplus
?}
#endif
#endif
這個(gè)頭文件會(huì)分別被DLL和調(diào)用DLL的應(yīng)用程序引入,當(dāng)被DLL引入時(shí),在DLL中定義_DLL_SAMPLE宏,這樣就會(huì)在DLL模塊中聲明函數(shù)為導(dǎo)出函數(shù);當(dāng)被調(diào)用DLL的應(yīng)用程序引入時(shí),就沒(méi)有定義_DLL_SAMPLE,這樣就會(huì)聲明頭文件中的函數(shù)為從DLL中的導(dǎo)入函數(shù)。
第二種方法:模塊定義文件是一個(gè)有著.def文件擴(kuò)展名的文本文件。它被用于導(dǎo)出一個(gè)DLL的函數(shù),和__declspec(dllexport)很相似,但是.def文件并不是Microsoft定義的。一個(gè).def文件中只有兩個(gè)必需的部分:LIBRARY 和 EXPORTS。
DESCRIPTION?"my simple DLL"
EXPORTS
?TestDLL @1?;@1表示這是第一個(gè)導(dǎo)出函數(shù)
第一行,''LIBRARY''是一個(gè)必需的部分。它告訴鏈接器(linker)如何命名你的DLL。下面被標(biāo)識(shí)為''DESCRIPTION''的部分并不是必需的。該語(yǔ)句將字符串寫(xiě)入 .rdata 節(jié),它告訴人們誰(shuí)可能使用這個(gè)DLL,這個(gè)DLL做什么或它為了什么(存在)。再下面的部分標(biāo)識(shí)為''EXPORTS''是另一個(gè)必需的部分;這個(gè)部分使得該函數(shù)可以被其它應(yīng)用程序訪問(wèn)到并且它創(chuàng)建一個(gè)導(dǎo)入庫(kù)。當(dāng)你生成這個(gè)項(xiàng)目時(shí),不僅是一個(gè).dll文件被創(chuàng)建,而且一個(gè)文件擴(kuò)展名為.lib的導(dǎo)出庫(kù)也被創(chuàng)建了。除了前面的部分以外,這里還有其它四個(gè)部分標(biāo)識(shí)為:NAME, STACKSIZE, SECTIONS, 和 VERSION。另外,一個(gè)分號(hào)(;)開(kāi)始一個(gè)注解,如同''//''在C++中一樣。定義了這個(gè)文件之后,頭文件中的__declspec(dllexport)就不需要聲明了。
三、編寫(xiě)DllMain函數(shù)和導(dǎo)出函數(shù)
DllMain函數(shù)是DLL模塊的默認(rèn)入口點(diǎn)。當(dāng)Windows加載DLL模塊時(shí)調(diào)用這一函數(shù)。系統(tǒng)首先調(diào)用全局對(duì)象的構(gòu)造函數(shù),然后調(diào)用全局函數(shù)DLLMain。DLLMain函數(shù)不僅在將DLL鏈接加載到進(jìn)程時(shí)被調(diào)用,在DLL模塊與進(jìn)程分離時(shí)(以及其它時(shí)候)也被調(diào)用。
#define?_DLL_SAMPLE
#ifndef _DLL_SAMPLE_H
#include?"DLLSample.h"
#endif
#include?"stdio.h"
//APIENTRY聲明DLL函數(shù)入口點(diǎn)
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch?(ul_reason_for_call)
{
case?DLL_PROCESS_ATTACH:
case?DLL_THREAD_ATTACH:
case?DLL_THREAD_DETACH:
case?DLL_PROCESS_DETACH:
break;
? }
return?TRUE;
?}
void?TestDLL(int?arg)
{
?printf("DLL output arg %d/n", arg);
?}
如果程序員沒(méi)有為DLL模塊編寫(xiě)一個(gè)DLLMain函數(shù),系統(tǒng)會(huì)從其它運(yùn)行庫(kù)中引入一個(gè)不做任何操作的缺省DLLMain函數(shù)版本。在單個(gè)線程啟動(dòng)和終止時(shí),DLLMain函數(shù)也被調(diào)用。
然后,F7編譯,就得到一個(gè)DLL了。
變量導(dǎo)出類似。
?
?
?
從 DLL 導(dǎo)出?
.DLL 文件的布局與 .exe 文件非常相似,但有一個(gè)重要的差異:DLL 文件包含導(dǎo)出表。導(dǎo)出表包含 DLL 導(dǎo)出到其他可執(zhí)行文件的每個(gè)函數(shù)的名稱。這些函數(shù)是 DLL 中的入口點(diǎn);只有導(dǎo)出表中的函數(shù)可由其他可執(zhí)行文件訪問(wèn)。DLL 中的任何其他函數(shù)都是 DLL 私有的。通過(guò)使用帶 /EXPORTS 選項(xiàng)的Dumpbin?工具,可以查看 DLL 的導(dǎo)出表。
有兩種從 DLL 導(dǎo)出函數(shù)的方法:
-
在生成 DLL 時(shí),創(chuàng)建一個(gè)模塊定義 (.def) 文件并使用該 .def 文件。如果希望按序號(hào)而不是按名稱從 DLL 導(dǎo)出函數(shù),則請(qǐng)使用此方法。
-
在函數(shù)的定義中使用?__declspec(dllexport)?關(guān)鍵字。
用上述任何方法導(dǎo)出函數(shù)時(shí),確保使用?__stdcall?調(diào)用約定。
? 使用 DEF 文件從 DLL 導(dǎo)出?
模塊定義 (.def) 文件是包含一個(gè)或多個(gè)描述 DLL 各種屬性的 Module 語(yǔ)句的文本文件。如果不使用__declspec(dllexport)?關(guān)鍵字導(dǎo)出 DLL 的函數(shù),則 DLL 需要 .def 文件。
.def 文件必須至少包含下列模塊定義語(yǔ)句:
-
文件中的第一個(gè)語(yǔ)句必須是 LIBRARY 語(yǔ)句。此語(yǔ)句將 .def 文件標(biāo)識(shí)為屬于 DLL。LIBRARY 語(yǔ)句的后面是 DLL 的名稱。鏈接器將此名稱放到 DLL 的導(dǎo)入庫(kù)中。
-
EXPORTS 語(yǔ)句列出名稱,可能的話還會(huì)列出 DLL 導(dǎo)出函數(shù)的序號(hào)值。通過(guò)在函數(shù)名的后面加上 @ 符和一個(gè)數(shù)字,給函數(shù)分配序號(hào)值。當(dāng)指定序號(hào)值時(shí),序號(hào)值的范圍必須是從 1 到 N,其中 N 是 DLL 導(dǎo)出函數(shù)的個(gè)數(shù)。如果希望按序號(hào)導(dǎo)出函數(shù),請(qǐng)參見(jiàn)按序號(hào)而不是按名稱從 DLL 導(dǎo)出函數(shù)以及本主題。
例如,包含實(shí)現(xiàn)二進(jìn)制搜索樹(shù)的代碼的 DLL 看上去可能像下面這樣:
?復(fù)制代碼 LIBRARY BTREE EXPORTSInsert @1Delete @2Member @3Min @4如果使用?MFC DLL 向?qū)?/span>創(chuàng)建 MFC DLL,則向?qū)槟鷦?chuàng)建主干 .def 文件并將其自動(dòng)添加到項(xiàng)目中。添加要導(dǎo)出到此文件的函數(shù)名。對(duì)于非 MFC DLL,必須親自創(chuàng)建 .def 文件并將其添加到項(xiàng)目中。
如果導(dǎo)出 C++ 文件中的函數(shù),必須將修飾名放到 .def 文件中,或者通過(guò)使用外部“C”定義具有標(biāo)準(zhǔn) C 鏈接的導(dǎo)出函數(shù)。如果需要將修飾名放到 .def 文件中,則可以通過(guò)使用?DUMPBIN?工具或?/MAP?鏈接器選項(xiàng)來(lái)獲取修飾名。請(qǐng)注意,編譯器產(chǎn)生的修飾名是編譯器特定的。如果將 Visual C++ 編譯器產(chǎn)生的修飾名放到 .def 文件中,則鏈接到 DLL 的應(yīng)用程序必須也是用相同版本的 Visual C++ 生成的,這樣調(diào)用應(yīng)用程序中的修飾名才能與 DLL 的 .def 文件中的導(dǎo)出名相匹配。
如果生成擴(kuò)展 DLL?并使用 .def 文件導(dǎo)出,則將下列代碼放在包含導(dǎo)出類的頭文件的開(kāi)頭和結(jié)尾:
?復(fù)制代碼 #undef AFX_DATA #define AFX_DATA AFX_EXT_DATA // <body of your header file> #undef AFX_DATA #define AFX_DATA這些代碼行確保內(nèi)部使用的 MFC 變量或添加到類的變量是從擴(kuò)展 DLL 導(dǎo)出(或?qū)?#xff09;的。例如,當(dāng)使用?DECLARE_DYNAMIC?派生類時(shí),該宏擴(kuò)展以將?CRuntimeClass?成員變量添加到類。省去這四行代碼可能會(huì)導(dǎo)致不能正確編譯或鏈接 DLL,或在客戶端應(yīng)用程序鏈接到 DLL 時(shí)導(dǎo)致錯(cuò)誤。
當(dāng)生成 DLL 時(shí),鏈接器使用 .def 文件創(chuàng)建導(dǎo)出 (.exp) 文件和導(dǎo)入庫(kù) (.lib) 文件。然后,鏈接器使用導(dǎo)出文件生成 DLL 文件。隱式鏈接到 DLL 的可執(zhí)行文件在生成時(shí)鏈接到導(dǎo)入庫(kù)。
請(qǐng)注意,MFC 本身使用 .def 文件從 MFCx0.dll 導(dǎo)出函數(shù)和類。
使用 __declspec(dllexport) 從 DLL 導(dǎo)出?
Microsoft 在 Visual C++ 的 16 位編譯器版本中引入了?__export,使編譯器得以自動(dòng)生成導(dǎo)出名并將它們放到一個(gè) .lib 文件中。然后,此 .lib 文件就可以像靜態(tài) .lib 那樣用于與 DLL 鏈接。
在更新的編譯器版本中,可以使用?__declspec(dllexport)?關(guān)鍵字從 DLL 導(dǎo)出數(shù)據(jù)、函數(shù)、類或類成員函數(shù)。__declspec(dllexport)?會(huì)將導(dǎo)出指令添加到對(duì)象文件中,因此您不需要使用 .def 文件。
當(dāng)試圖導(dǎo)出 C++ 修飾函數(shù)名時(shí),這種便利最明顯。由于對(duì)名稱修飾沒(méi)有標(biāo)準(zhǔn)規(guī)范,因此導(dǎo)出函數(shù)的名稱在不同的編譯器版本中可能有所變化。如果使用?__declspec(dllexport),僅當(dāng)解決任何命名約定更改時(shí)才必須重新編譯 DLL 和依賴 .exe 文件。
許多導(dǎo)出指令(如序號(hào)、NONAME 和 PRIVATE)只能在 .def 文件中創(chuàng)建,并且必須使用 .def 文件來(lái)指定這些屬性。不過(guò),在 .def 文件的基礎(chǔ)上另外使用?__declspec(dllexport)?不會(huì)導(dǎo)致生成錯(cuò)誤。
若要導(dǎo)出函數(shù),__declspec(dllexport)?關(guān)鍵字必須出現(xiàn)在調(diào)用約定關(guān)鍵字的左邊(如果指定了關(guān)鍵字)。例如:
?復(fù)制代碼 __declspec(dllexport) void __cdecl Function1(void);若要導(dǎo)出類中的所有公共數(shù)據(jù)成員和成員函數(shù),關(guān)鍵字必須出現(xiàn)在類名的左邊,如下所示:
?復(fù)制代碼 class __declspec(dllexport) CExampleExport : public CObject { ... class definition ... };生成 DLL 時(shí),通常創(chuàng)建一個(gè)包含正在導(dǎo)出的函數(shù)原型和/或類的頭文件,并將?__declspec(dllexport)?添加到頭文件中的聲明中。若要提高代碼的可讀性,請(qǐng)為?__declspec(dllexport)?定義一個(gè)宏并對(duì)正在導(dǎo)出的每個(gè)符號(hào)使用該宏:
?復(fù)制代碼 #define DllExport __declspec( dllexport )__declspec(dllexport)?將函數(shù)名存儲(chǔ)在 DLL 的導(dǎo)出表中。如果希望優(yōu)化表的大小,請(qǐng)參見(jiàn)按序號(hào)而不是按名稱從 DLL 導(dǎo)出函數(shù)。
| 將 DLL 源代碼從 Win16 移植到 Win32 時(shí),請(qǐng)用?__declspec(dllexport)?替換?__export?的每個(gè)實(shí)例。 |
轉(zhuǎn)載于:https://www.cnblogs.com/yangxx-1990/p/4847459.html
總結(jié)
以上是生活随笔為你收集整理的DLL 的导入与导出的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 20150928所学粗略整理
- 下一篇: Android:简单的弹幕效果达到