在上周完成的一項工作中,發現了一個當時有點困惑的問題:
當編寫供VB調用的C++ DLL時,必須使用def檔案來定義匯出符號,否則VB程式將連結失敗。我們都知道使用def檔案是為了避免C++編譯器對函數進行重新命名,另一個達到相同效果的替代方式是使用extern "C"標識符。若DLL由C++、C#、Java等調用,後者可以工作得很好;然而VB不可以,即便使用了extern "C"來標識匯出函數,依然需要定義def檔案。
就在剛剛,我找到了答案,《Windows核心編程》——19.3.2 建立用於非Visual C++工具的DLL
根據其中說明,可知:當使用__stdcall調用方式時,就算使用了extern "C",編譯器也依然會更改函數名。因此會導致連結失敗。
文中也提到了不依賴def檔案解決此問題的一種方法。
原文如下:
如果使用Microsoft Visual C++來建立D L L和將要連結到該D L L的可執行模組,可以跳過本節內容的學習。但是,如果使用Visual C++建立D L L,而這個D L L要連結到使用任何供應商的工具建立的可執行模組,那麼必須做一些額外的工作。
前面講過當進行C和C + +混合編程時使用e x t e r n“C”修改符的問題。也講過C + +類的問題以及為什麼因為名字改變的緣故你必須使用同一個編譯器供應商的工具的問題。當你直接將C語言編程用於多個工具供應商時將會出現另一個問題。這個問題是,即使你根本不使用C + +,M i c r o s o f t的C編譯器也會損害C函數。當你的函數使用_ _ s t d c a l l ( W I N A P I )調用規則時會出現這種問題。這種調用規則是最流行的一種類型。當使用_ _ s t d c a l l將C函數輸出時,M i c r o s o f t的編譯器就會改變函數的名字,設定一個前置底線,再加上一個@符號的首碼,後隨一個數字,表示作為參數傳遞給函數的位元組數。例如,下面的函數是作為D L L的輸出節中的_ M y F u n c @ 8輸出的:
__declspec(dllexport) LONG __stdcall MyFunc(int a, int b);
如果用另一個供應商的工具建立了一個可執行模組,它將設法連結到一個名叫M y F u n c的函數,該函數在M i c r o s o f t編譯器已有的D L L中並不存在,因此連結將失敗。
若要使用與其他編譯器供應商的工具鏈接的M i c r o s o f t的工具建立一個可執行模組,必須告訴M i c r o s o f t的編譯器輸出沒有經過改變的函數名。可以用兩種方法來進行這項操作。第一種方法是為編程項目建立一個. d e f檔案,並在該. d e f檔案中加上類似下面的E X P O RT S節:
EXPORTS
MyFunc
當M i c r o s o f t的連結程式分析這個. d e f檔案時,它發現_ M y F u n c @ 8和M y F u n c均被輸出。由於這兩個函數名是互相匹配的(除了截斷的尾部外),因此連結程式使用M y F u n c的. d e f檔案名稱來輸出該函數,而根本不使用_ M y F u n c @ 8的名字來輸出函數。
現在你可能認為,如果使用M i c r o s o f t的工具建立一個可執行模組,並且設法將它連結到包含未截斷名字的D L L,那麼連結程式的運行將會失敗,因為它將試圖連結到稱為_ M y F u n c @ 8的函數。當然,你會高興地瞭解到M i c r o s o f t的連結程式進行了正確的操作,將可執行模組連結到名字為M y F u n c的函數。
如果想避免使用. d e f檔案,可以使用第二種方法輸出未截斷的函數版本。在D L L的原始碼模組中,可以添加下面這行代碼:
#pragma comment(linker, "/export:MyFunc=_MyFunc@8")
這行代碼使得編譯器發出一個連結程式指令,告訴連結程式,一個名叫M y F u n c的函數將被輸出,其進入點與稱為_ M y F u n c @ 8的函數的進入點相同。第二種方法沒有第一種方法容易,因為你必須自己截斷函數名,以便建立該程式碼。另外,當使用第二種方法時, D L L實際上輸出用於標識單個函數的兩個符號,即M y F u n c和_ M y F u n c @ 8,而第一種方法只輸出符號M y F u n c。第二種方法並沒有給你帶來更多的好處,它只是使你可以避免使用. d e f的檔案而已。