程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 制作C/C++動態鏈接庫(dll)若干注意事項,動態鏈接庫dll

制作C/C++動態鏈接庫(dll)若干注意事項,動態鏈接庫dll

編輯:C++入門知識

制作C/C++動態鏈接庫(dll)若干注意事項,動態鏈接庫dll


一、C\C++ 運行時庫編譯選項簡單說明

問題:我的dll別人沒法用

運行時庫是個很復雜的東西,作為開發過程中dll制作需要了解的一部分,這裡主要簡單介紹一下如何選擇編譯選項。

在我們的開發過程中時常會遇到這樣的問題:

1. 我的VS版本比較高(比如:VS2012),我想制作一個dll,封裝了幾個函數給別人用。

2. 打包後發現我的dll引用了msvcr110.dll或者msvcr110d.dll,這個dll別人電腦可能沒有。

3. 於是別人使用時出現了諸如:“無法在DLL“XXXX.dll”中找到名為“XXXX()”的入口點”等問題。

最終結果就是,反復檢查發現都沒有錯,用工具查看也發現函數確實已經導出了,但是別人就沒法用。

這裡可能就需要對編譯選項進行修改了。

解釋:如何避免上述問題

在VS中打開:項目屬性——>配置屬性——>C/C++——>代碼生成——>運行時。其中可以看到多個選項,如下圖所示:

選項 說明 /MD

使應用程序使用運行時庫的多線程並特定於 DLL 的版本。定義 _MT_DLL,並使編譯器將庫名 MSVCRT.lib 放入 .obj 文件中。

用此選項編譯的應用程序靜態鏈接到 MSVCRT.lib。該庫提供允許鏈接器解析外部引用的代碼層。實際工作代碼包含在 MSVCR80.DLL 中,該庫必須在運行時對於與 MSVCRT.lib 鏈接的應用程序可用。

當在定義了 _STATIC_CPPLIB (/D_STATIC_CPPLIB) 的情況下使用 /MD 時,它將導致應用程序與靜態多線程標准 C++ 庫 (libcpmt.lib) 而非動態版本 (msvcprt.lib) 鏈接,同時仍通過 msvcrt.lib 動態鏈接到主 CRT。

/MDd 定義 _DEBUG_MT_DLL,並使應用程序使用運行時庫的調試多線程並特定於 DLL 的版本。它還使編譯器將庫名 MSVCRTD.lib 放入 .obj 文件中。 /MT 使應用程序使用運行時庫的多線程靜態版本。定義 _MT 並使編譯器將庫名 LIBCMT.lib 放入 .obj 文件中,以便鏈接器使用 LIBCMT.lib 解析外部符號。 /MTd 定義 _DEBUG_MT。此選項還使編譯器將庫名 LIBCMTD.lib 放入 .obj 文件中,以便鏈接器使用 LIBCMTD.lib 解析外部符號。 /LD

創建 DLL。

將 /DLL 選項傳遞到鏈接器。鏈接器查找 DllMain 函數,但並不需要該函數。如果沒有編寫 DllMain 函數,鏈接器將插入返回 TRUE 的DllMain 函數。

鏈接 DLL 啟動代碼。

如果命令行上未指定導出 (.exp) 文件,則創建導入庫 (.lib);將導入庫鏈接到調用您的 DLL 的應用程序。

將 /Fe(命名 EXE 文件)解釋為命名 DLL 而不是 .exe 文件;默認程序名成為 basename.dll 而不是 basename.exe。

除非顯式指定 /MD,否則將暗指 /MT

/LDd

  創建調試 DLL。定義 _MT_DEBUG

就從VS的dll庫的編譯選項來說就前面四項,/MD、/MDd、/MT和/MTd。其中/MDd、/MTd後面的“d”表示編譯生成的是Debug版本,也就是用於編譯生成Debug版本的程序;不加“d”表示是Release版本的程序,如果此時你把項目配置成Debug版本,編譯會不通過。

動態鏈接多線程庫(MD/MDd)

動態鏈接的運行時庫,此時將msvcrt.lib安置到obj文件中,它連接到dll的方式是靜態鏈接,實際上工作的庫是msvcrxx.dll。所有的 C 庫函數保存在動態鏈接庫 msvcrXX.dll中, 由msvcrXX.dll處理多線程問題。也就是說,這種編譯方式下我們是通過msvcrXX.dll這個動態鏈接庫去鏈接CRT。

此時我們編譯的dll引用了msvcrXX.dll文件,這個文件在使用的時候必須能夠被計算機查詢到。比如:我的電腦編譯引用了msvcr110.dll,那麼我的dll給別人調用的時候,對方計算機一定要有msvcr110.dll,否則dll庫就無法使用。因此這種編譯方式,必須將msvcr110.dll一同攜帶過去,並且要在對方計算機上進行注冊。

使用此方式編譯時,使用Depends查看,我們可以看到dll 引用了msvcr110.dll,如下圖:

 

#define SW_REV extern "C" _declspec(dllexport) SW_REV int __stdcall add(int a, int b); int __stdcall add(int a, int b) { return a+b; }

 

_cdecl調用

_cdecl是C/C++的缺省調用方式,參數采用從右到左的壓棧方式,傳送參數的內存棧由調用者維護。_cedcl約定的函數只能被C/C++調用,每一個調用它的函數都包含清空堆棧的代碼,所以產生的可執行文件大小會比調用_stdcall函數的大。

在不加修飾的情況下,VC++默認使用這種調用方式,形式如下(默認,可省略):

#define SW_REV extern "C" _declspec(dllexport)

SW_REV int add(int a, int b);

int add(int a, int b)
{
    return a+b;
}

什麼時候區分?

其實兩種方式都一樣,區別在於調用。例如:有些語言要求調用時只認_stdcall,那麼就只能按照要求使用_stdcall版本的dll。很多情況下,調用方式都可選擇,說白了,就是一定要保持一致,例如C#調用上述_stdcall方式的dll:

[DllImport("SwLib.dll", EntryPoint = "add", CallingConvention = CallingConvention.StdCall)]
        private static extern int add(int a, int b);

這裡CallingConvention需要選擇CallingConvention.StdCall,如果我將其改成 CallingConvention.Cdecl,程序就會報錯,指出堆棧調用不對稱:

image

 

PS:寫這個東西真的好費勁,上次寫到現在好久了,有好多想記錄一下的東西,發現自己懶得寫,好懶!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved