最近恰好需要用 C++ 實現一個供 C# .NET 調用的模塊,用dllexport導出符號的時候出現了一點問題,明明已經看到了導出符號,但是 .NET 在調用的時候,就是找不到方法。然后用 def 文件的方式導出符號就正常,突然對這兩種方式的區別產生興趣,之前一直沒有研究過,就仔細了查閱一番。
以導出名為 TESTFUNC 方法為例:
def 文件方式
,正常在程序中定義方法,然后新建一個 .def 文件,內容類似
LIBRARY
EXPORT
TESTFUNC
在其他工程使用這個方法的時候需要頭文件,然后在連接時指定 .def 文件目錄。
dllexport方式
在定義 TESTFUNC 方法時,在聲明前加上 __declspec(dllexport)。
區別
這里就不得不說導出符號在 DLL 中的形式。實際上對于 C++ 來說,當導出的時候,不會以原名導出,因為會加上一些符號字母后綴,實際上如果了解 C++ 的人,也會知道 C++ 在處理函數重載的時候,其實也用了這個套路,實際上編譯之后就沒有重載的概念了,而是根據參數生成了獨一無二的方法名。
那說回來既然名字不同,那為什么其他模塊調用還沒問題呢。回答這個問題之前要先知道其他模塊如何引用。
調用導出函數的方式
一般有三種形式:
- .h 提供聲明之后,直接調用,在連接的時候指定 .def 文件目錄;
- .h 提供聲明之后,直接調用,鏈接的時候指定 .lib 文件地址
- 內部聲明要調用函數的函數指針,loadlibrary 之后,直接取到函數地址,調用函數;
了解了這三種方式之后,就可以回答上邊的問題。
- 對于1、2兩種方式,由編譯器自動轉換函數名,尋找到正確的地址,鏈接之;
- 對于第3種方式,如果不把真正的函數方法名寫對,就找不到函數了。
所以其他模塊調用沒問題。
話說回來,那也不可能每次都把@那些符號寫對。所以會看到有時候導出的時候_extern "C" _declspec(dllexport)這樣寫,這是為了讓函數以 C 的方式來編譯,這樣導出的方法就是沒有那些符號的了,但這樣有個問題,就是函數必須以 C 方式調用,而且也不能用來導出類對象,原因是顯而易見的……
其實當了解上邊之后,不難發現,1、2才是我們最想要的,3就很局限。這樣問題就來了,1、2兩種方式又有什么區別呢:
區別就在這個 .lib 上,如果在 C++ 或者 C 工程這個范圍來說,確實沒區別。但是假如調用工程不是 C++ 工程呢,他就是個 C# 工程呢,他是沒辦法用 .lib 的。
綜上所述:.def 文件的方式才是最通用的做法。那回到我最初的問題,我的 C# 工程之所以在調用使用 dllexport導出的方法失敗,就是因為,我沒有寫對真正的方法名(帶一堆符號的那個)。而使用 .def 文件的話,就沒有這個問題了。