1. 程式人生 > >動態連結庫(dll)檔案解析

動態連結庫(dll)檔案解析

生成動態連結庫(dll檔案)

1、使用VS生成動態連結庫的步驟:
(1)新建一個win32控制檯工程,並在應用程式設定視窗中選擇“Dll”選項,附加選項選擇“空專案”。如下圖:

這裡寫圖片描述

(2)建立完工程之後,新增原始檔,在原始檔中寫上想匯出到dll檔案的函式。函式宣告之前應該加上“_declpec(dllexport)”表示函式輸出為動態連結庫。除此之外,還要在函式名前面加上呼叫約定。因為c/c++語言預設的呼叫約定是“_cdecl”,如果採用“_cdecl”呼叫約定,可以不用寫。如果使用“_stdcall”和“_fastcall”呼叫約定,則要進行說明。下圖是一個簡單的例子:

這裡寫圖片描述

圖中有三個函式,分別採用的呼叫約定是“_cdecl”,”_stdcall”和”_fastcall”。呼叫約定會給函式名加上一些修飾,不同的呼叫約定給函式名的修飾是不一樣的,因此要慎重地使用呼叫約定。
(3)編譯。在選單欄上的“生成”中點選“生成解決方案”即可生成動態連結庫。如果編譯成功,到工程資料夾下面的Debug資料夾裡頭可以找到字尾名為dll和lib連個檔案。其中,lib檔案儲存著函式的相關定義和索引,其作用類似於標頭檔案,而dll檔案是函式的實現部分,是不可缺少的。

2、生成動態連結庫時應注意的事項
(1)函式宣告前面加上“_declspec(dllexport)”表明函式將輸出為動態連結庫,是必不可少的,
(2)匯出的函式如果不是採用C/C++預設的“_cdecl”的呼叫約定,則要特別說明。使用呼叫約定時,應考慮到以後呼叫該函式的問題,呼叫時使用的呼叫約定只有與生成時設定的呼叫約定相一致時,才能呼叫。也就是說,如果生成dll檔案時,給函式設定的呼叫約定為“_stdcall”,而呼叫該函式時使用的呼叫約定是“_cdecl”,那麼將會無法找到該函式。
(3)在相同的呼叫約定下,採用不同的編譯器,對函式名的修飾是不一樣的。比如,同是採用”_cdecl”呼叫約定,C語言和C++語言匯出的dll檔案中,函式的修飾名是不一樣的。如果要C語言風格的dll檔案,就要再加上“extern C”進行修飾,或者把原始檔名的字尾改為.c。如果是要C++風格的dll檔案,則原始檔名字尾必須為.cpp。下圖是生成C風格的dll檔案例子:

這裡寫圖片描述

前兩個函式將會匯出為C風格的dll,而後一個函式被匯出為C++風格的dll。如果把原始檔字尾改為.c,那麼所有的函式都會被匯出為C風格的dll。

呼叫動態連結庫

1、C語言呼叫C語言的dll檔案
如圖,有三個函式被匯出到dll,前兩個是C語言風格的,後一個是C++風格的。C語言是無法用常規方法呼叫C++風格的dll。

這裡寫圖片描述

(1)新建一個控制檯工程,新增一個原始檔,並將原始檔的字尾改為.c,告訴編譯器這是一個C語言程式。
(2)將lib檔案和dll檔案放在與原始檔相同的目錄下。
(3)在程式的開頭要加上#pragma comment(lib,”mydll.lib”),第一個引數必須是lib,第二個個引數是lib檔案的檔名。函式呼叫前要先宣告,函式的宣告需要加上呼叫約定修飾。如下圖:

這裡寫圖片描述

(4)生成解決方案,如果沒有錯誤,執行程式將會輸出正確的結果。

2、C++呼叫C語言的dll
在C++程式中,要呼叫C語言的dll,要宣告一下呼叫的函式是C語言風格的。方法是在函式宣告時加上 extern “C”修飾。新建一個控制檯工程,新增一個cpp檔案,原始檔中的程式碼如圖所示:

這裡寫圖片描述

3、C++呼叫C++的dll

C++呼叫C++的dll,只需在函式宣告時加上呼叫約定修飾。如下圖:

這裡寫圖片描述

4、動態載入dll
以上的呼叫dll的方法都是屬於靜態呼叫型別的,一般是需要有lib檔案的。如果採用動態載入dll,則不需要lib檔案,只需dll檔案就足夠了。動態載入dll需要用到兩個函式,一個是LoadLibrary,另一個是GetProcAddress,這兩個函式都包含在window.h標頭檔案中。值得注意的是,動態載入dll檔案的方法,一般只能呼叫C語言風格的,且呼叫約定為“_cdecl”的函式。下圖是動態載入dll的例子:

這裡寫圖片描述

上面說,動態載入dll的方法一般適合呼叫C風格的、且呼叫約定為“_cdecl”的函式,那是因為C風格的、且呼叫約定為“_cdecl”的函式的函式名不會被修飾,原始檔寫的是什麼樣子,dll檔案中就是什麼樣子。當然,呼叫C++風格的,且不是“_cdecl”約定的函式也是可以的,只是很麻煩。由於編譯器型別(C或C++)和呼叫約定都會對函式的名稱進行修飾,使得dll檔案中的函式名稱不再是原始檔所寫的那樣。GetProcAddress函式是通過函式名來查詢函式入口的,因此,只要知道dll檔案中的函式修飾名,將函式修飾名傳給GetProcAddress函式,就可以獲得函式的指標。那麼如何知道dll檔案中函式的修飾名呢?這就需要用到一些分析軟體了,比如depends這個軟體就可以檢視dll檔案的函式名稱。下圖是使用depends軟體檢視dll中的函式。可以看到Add和Multi函式在dll檔案中的修飾名分別是[email protected]@[email protected][email protected]@[email protected]

這裡寫圖片描述

是不是所有的函式都可以通過查詢其在dll檔案中的修飾名來獲取函式指標呢?為此,我做了一些實驗,實驗未必充分,但也可以得出一些結論:
(1)往GetProcAddress函式中傳入函式在dll的修飾名,如果dll中的函式採用的是“_cdecl”呼叫約定,無論是C風格的還是C++風格的,都不會報錯,函式呼叫的結果也是正確的。下圖是呼叫採用“_cdecl”呼叫約定的函式:

這裡寫圖片描述

這裡寫圖片描述

從實驗結果來看,對於呼叫約定為“_cdecl”的函式,只要能通過depends找到函式的修飾名,就可以呼叫該函式。函式呼叫的結果是正確的。

(2)往GetProcAddress函式中傳入函式在dll的修飾名,如果dll中的函式採用的是“_stdcall”呼叫約定,程式執行時會報錯,但是忽略錯誤,呼叫的結果卻是正確的。下圖是呼叫“_stdcall”約定的函式:

這裡寫圖片描述
這裡寫圖片描述

從實驗結果來看,呼叫“_stdcall”約定的函式,是會報錯的,但結果仍然正確。

(3)往GetProcAddress函式中傳入函式在dll的修飾名,如果dll中的函式採用的是“_fastcall”呼叫約定,那麼程式執行不會報錯,但呼叫結果卻是錯誤的!下圖是呼叫“_fastcall”約定的函式:

這裡寫圖片描述
這裡寫圖片描述

很讓人鬱悶的是,呼叫“_fastcall”約定的函式,程式執行時不會報錯,但是呼叫的結果卻是錯得離譜。2+3=-1672607445這是什麼鬼?原因不明。

(4)結論:通過使用LoadLibrary函式和GetProcAddress函式來動態載入dll檔案,這種方法只適用於呼叫“_cedcl”約定的函式,只要是採用“_cdecl”約定的,不管是C風格的還是C++風格的,都可以正常地被呼叫。如果是其他呼叫約定,無論是C風格還是C++風格的函式,都無法正常呼叫。