使用Dependency看DLL的導(dǎo)出函數(shù)的名字,會發(fā)現(xiàn)有一些有意思的東西,這大多是和編譯DLL時候指定DLL導(dǎo)出函數(shù)的導(dǎo)出符有關(guān)系。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
當(dāng)你使用extern "C"的情況下:
__stdcall會使導(dǎo)出函數(shù)名字前面加一個下劃線,后面加一個@再加上參數(shù)的字節(jié)數(shù),比如_Fun@4就是4個字節(jié)
__fastcall類似__stdcall,不過前面沒有下劃線,_fastcall應(yīng)該前面還有一個@,比如@LoadaDir@4
__cdecl則是前面僅僅有一個下劃線
如果不用extern "C"話則使用C++命名機(jī)制,涉及到C++ NameMangling,比較復(fù)雜,編譯器之間也不太一樣。
另外,__declspec(dllexport)僅會對__cdecl進(jìn)行處理,去掉前面的下劃線(對于一般全局函數(shù)來說缺省就是__cdecl),而對于其他兩種不會處理。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
extern"C"的作用是(防止C++編譯器的“名字破壞”特性),使編譯器按照C的方式生成函數(shù)名,C的方式實際的函數(shù)名和你寫的一樣。如果沒有這個,則按照C++的方式生成函數(shù)名,這樣實際的函數(shù)名(LoadLibrary方式GetProcAddress傳入的函數(shù)名)和你寫得函數(shù)名不一樣,這樣你用LoadLibrary、GetProcAddress這種方式調(diào)用dll就不成功。
但是用引入庫(*.LIB)的方式調(diào)用,則編譯器自動轉(zhuǎn)換函數(shù)名,所以總是沒有問題。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
我們知道為了讓DLL導(dǎo)出一些函數(shù),需要在每一個將要被導(dǎo)出的函數(shù)前面添加標(biāo)識符:_declspec(dllexport)。例如在DLL中可以導(dǎo)出這樣的函數(shù)(方法)
#define DLL1_API _declspec(dllexport)
DLL1_API int Add(int a,int b)
{
return a+b;
}
現(xiàn)在我們解決名字改編問題,C++編譯器在生成DLL時,會對導(dǎo)出的函數(shù)進(jìn)行名字改編,并且不同的編譯器使用的改編規(guī)則不一樣,因此改編后的名字也是不同的。這樣,如果利用不同編譯器分別生成DLL和訪問DLL的客戶端程序,后者在訪問該DLL的導(dǎo)出函數(shù)時就會出現(xiàn)問題。如上例中函數(shù)Add在C++編譯器改編后的名字是?Add@@YAHHH@Z。我們希望編譯后的名字不發(fā)生改變,這里有幾種方法。
第一種是定義導(dǎo)出函數(shù)時加上限定符:extern "C"
#define DLL1_API extern "C" _declspec(dllexport)
但extern"C"只解決了C和C++語方之間調(diào)用的問題,它只能用于導(dǎo)出全局函數(shù)這種情況而不能導(dǎo)出一個類的成員函數(shù)。另外如果導(dǎo)出函數(shù)的調(diào)用約定發(fā)生改變,即使使用了extern"C",編譯后的函數(shù)名還是會發(fā)生改變。比如我們加入_stdcall關(guān)鍵字說明調(diào)用約定為C調(diào)用約定(標(biāo)準(zhǔn)調(diào)用約定,也就是WINAPI調(diào)用約定)。
#define DLL1_API extern "C" _declspec(dllexport)
DLL1_API int _stdcall Add(int a,int b)
{
return a+b;
}
編譯后函數(shù)名Add改編成了_Add@8
第二種方法是通過一個稱為模塊定義文件DEF來解決。
LIBRARY dllname
EXPORTS
Add
Subtract
LIBRARY用來指定動態(tài)鏈接庫內(nèi)部名稱。該名稱與生成的動態(tài)鏈接庫名一定在匹配,這句代碼不是必須的。EXPORTS說明了DLL將要導(dǎo)出的函數(shù),以及為這些導(dǎo)出函數(shù)指定的符號名。
通過第二種方法模塊定義文件的方式DLL編譯后導(dǎo)出函數(shù)名不會發(fā)生改變。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
傳統(tǒng)的導(dǎo)出 DLL 函數(shù)的方法是使用模塊定義文件 (.def),Visual C++ 提供了更簡潔方便的方法,即“__declspec(dllexport)” 關(guān)鍵字,例如:
__declspec(dllexport) int __stdcall MyExportFunction(intiTest);
但是通過查看工具我們可以發(fā)現(xiàn),DLL 導(dǎo)出的函數(shù)名字實際上是 _MyExportFunction@4。還好,VC提供了一個預(yù)處理指示符 “#pragma” 來指定鏈接選項,可以通過它達(dá)到我們的目的,如下:
#pragma comment(linker,"/EXPORT:MyExportFunction=_MyExportFunction@4")
這樣再看,就會發(fā)現(xiàn)導(dǎo)出的函數(shù)名字已經(jīng)成為了想要的MyExportFunction。
終于知道了,應(yīng)該把函數(shù)前面的 __declspec() 修飾去掉,也就是說,只需要第二條 pragma指令即可。而且還可以使如下形式:
#pragma comment(linker,"/EXPORT:MyExportFunction=_MyExportFunction@4,PRIVATE")
PRIVATE 的作用與其在 def 文件中的作用一樣。