DLL庫的編寫(匯出、匯入)與使用

wan_young發表於2016-04-17

DLL庫的編寫(匯出、匯入)與使用

相關說明:

(1) 編寫dll時,為什麼用 extern “C”

因為CC++的重新命名規則是不一樣的。這種重新命名稱為“Name-Mangling”。據說C++標準並沒有規定Name-Mangling的方案,所以不同編譯器使用的是不同的,例如:Borland C++Microsoft C++就不同,而且可能不同版本的編譯器他們的Name-Mangling規則也是不同的。這樣的話,不同編譯器編譯出來的目標檔案.obj 是不通用的,因為同一個函式,使用不同的Name-Manglingobj檔案中就會有不同的名字。如果DLL裡的函式重新命名規則跟DLL的使用者採用的重新命名規則不一致,那就會找不到這個函式。

C標準規定了C語言Name-Mangling的規範。這樣就使得,任何一個支援C語言的編譯器,它編譯出來的obj檔案可以共享,連結成可執行檔案。這是一種標準,如果DLL跟其使用者都採用這種約定,那麼就可以解決函式重新命名規則不一致導致的錯誤。

影響符號名的除了C++C的區別、編譯器的區別之外,還要考慮呼叫約定導致的Name Mangling。如extern c _stdcall的呼叫方式就會在原來函式名上加上表示引數的符號,而extern c _cdecl則不會附加額外的符號。

dll中的函式在被呼叫時是以函式名或函式編號的方式被索引的。

所以綜上。若採用某編譯器的C++Name-Mangling方式產生的dll檔案可能不通用。因為它們的函式名重新命名方式不同。為了使得dll可以通用些,很多時候都要使用CName-Mangling方式,即是對每一個匯出函式宣告為extern C”,而且採用_stdcall呼叫約定,接著還需要對匯出函式進行重新命名,以便匯出不加修飾的函式名。

注意到extern C”的作用是為了解決函式符號名的問題,這對於動態連結庫的製造者和動態連結庫的使用者都需要遵守的規則。但extern "C"只解決了CC++語言之間呼叫的問題(extern C”是告訴編譯器,讓它按C的方式編譯),它只能用於匯出全域性函式這種情況而不能匯出一個類的成員函式。

 

2_declspec(dllexport)_declspec(dllimport)的作用:

_declspec還有另外的用途,這裡只討論跟dll相關的使用。正如括號裡的關鍵字一樣,匯出和匯入。

       因為dll中必須說明函式要用於匯出,所以_declspec(dllexport)很有必要。但是可以換一種方式,可以使用def檔案來說明哪些函式用於匯出,同時def檔案裡邊還有函式的編號。而使用_declspec(dllimport)卻不是必須的,但是建議這麼做。因為如果不用_declspec(dllimport)來說明該函式是從dll匯入的,那麼編譯器就不知道這個函式到底在哪裡,生成的exe裡會有一個call XX的指令,這個XX是一個常數地址,XX地址處是一個jmp dword ptr[XXXX]的指令,跳轉到該函式的函式體處【可理解為先到一個函式表裡找到具體的函式體地址,然後跳轉到該地址執行】,顯然這樣就無緣無故多了一次中間的跳轉。如果使用了_declspec(dllimport)來說明,那麼就直接產生call dword ptr[XXX],這樣就不會有多餘的跳轉了。

 

3_sdtcall: 這是一種函式的呼叫方式。預設情況下VC使用的是_cdecl的函式呼叫方式,如果產生的dll只會給C/C++程式使用,那麼就沒必要定義為_stdcall呼叫方式,如果要給Win32彙編使用(或者其他的_stdcall呼叫方式的程式),那麼就應該使用_stdcall。這個可能不是很重要,因為可以自己在呼叫函式的時候可以設定函式呼叫的規則。像VC就可以設定函式的呼叫方式,所以可以方便的使用win32彙編產生的dll不過_stdcall這呼叫約定會Name-Mangling,所以我覺得用VC預設的呼叫約定簡便些。但是,如果既要_stdcall呼叫約定,又要函式名不給修飾,那可以使用*.def檔案,或者在程式碼裡#pragma的方式給函式提供別名(這種方式需要知道修飾後的函式名是什麼)。

舉例:

·extern C_declspec(dllexport) bool  _stdcall cswuyg();

·extern C_declspec(dllimport) bool  _stdcall cswuyg();

·#pragma comment(linker,"/export:cswuyg=_cswuyg@0")

 

4.def檔案的規則為:

①LIBRARY語句說明.def檔案相應的DLL

②EXPORTS語句後列出要匯出函式的名稱。可以在.def檔案中的匯出函式名後加@n,表示要匯出函式的序號為n(在進行函式呼叫時,這個序號將發揮其作用);

③.def 檔案中的註釋由每個註釋行開始處的分號 (;) 指定,且註釋不能與語句共享一行。

*.def檔案只負責修改函式名稱,不負責呼叫約定。

舉例def檔案格式:

LIBRARY  XX(dll名稱這個並不是必須的,但必須確保跟生成的dll名稱一樣)

EXPORTS

[函式別名]=[函式名](要匯出的函式) @ [函式序號]

如果把*.def檔案加入到工程之後,連結的時候還需把它加進去。那麼可以這樣做:

手動的在link新增:

1)工程的propertiesConfiguration PropertiesLinkerCommand Line→在    “Additional options”里加上:/def:[完整檔名].def

2)工程的propertiesConfiguration PropertiesLinkerInputModule Definition File 里加上[完整檔名].def


附:*.exedll的順序是1)程式的當前目錄;2windows目錄下的系統目錄是c:/windows/system323Windows目錄;4PATH環境變數中列出的目錄

相關文章