最近項目中使用到了DLL,所以就把最近一段時間的學習總結一下,以備不時之需。編程
自從微軟推出第一個版本的Windows操做系統以來,動態連接庫(DLL)一直是Windows操做系統的基礎。動態連接庫一般都不能直接運行,也不能接收消息。它們是一些獨立的文件,其中包含能被可執行程序或其它DLL調用來完成某項工做的函數。只有在其它模塊調用動態連接庫中的函數時,它才發揮做用。WindowsAPI中的全部函數都包含在DLL中。其中有3個最重要的DLL,Kernel32.dll,它包含用於管理內存、進程和線程的各個函數;User32.dll,它包含用於執行用戶界面任務(如窗口的建立和消息的傳送)的各個函數;GDI32.dll,它包含用於畫圖和顯示文本的各個函數。編程語言
靜態庫:函數和數據被編譯進一個二進制文件(一般擴展名爲.LIB)。在使用靜態庫的狀況下,在編譯連接可執行文件時,連接器從庫中複製這些函數和數據並把它們和應用程序的其它模塊組合起來建立最終的可執行文件(.EXE文件)。在使用動態庫的時候,每每提供兩個文件:一個引入庫和一個DLL。引入庫包含被DLL導出的函數和變量的符號名,DLL包含實際的函數和數據。在編譯連接可執行文件時,只須要連接引入庫,DLL中的函數代碼和數據並不複製到可執行文件中,在運行的時候,再去加載DLL,訪問DLL中導出的函數。函數
能夠採用多種編程語言來編寫。
加強產品的功能。
提供二次開發的平臺。
簡化項目管理。
能夠節省磁盤空間和內存。
有助於資源的共享。
有助於實現應用程序的本地化。工具
隱式連接
顯示加載學習
在頭文件的最前面加上一下這樣一段代碼測試
/** 方法一 */ #ifdef DLL_EXPORT #define DLL_API __declspec(dllexport) #else #define DLL_API __declspec(dllimport) #endif // DLL_EXPORT /** 方法二 */ #ifdef DLL_EXPORT #define DLL_API __declspec(dllexport) #else #define DLL_API #endif // DLL_EXPORT
方法一的意思是根據是否認義DLL_EXPORT而將DLL_API分別定義爲__declspec(dllexport)或是__declspec(dllimport) ,用來表示DLL_API修飾的部分是導入仍是導出。由於DLL的頭文件在編譯DLL的時候須要spa
聲明導出方式,當用戶使用已發佈的DLL連接庫的時候,DLL的頭文件應該做爲導入方式使用,所以在編譯DLL庫的時候,須要在#include 「dll.h」的前面加入一句#define DLL_EXPORT,而動態庫的使用方則操作系統
不須要任何改變。線程
方法二將DLL_API定義爲空,也能夠,可是當導出類的靜態對象的時候,後出現問題,所以推薦第一種方法。設計
函數導出有兩種方式,第一種使用__declspec(dllexport)聲明導出函數,第二種使用使用.def文件聲明。
沒必要與包含新函數的新導入庫從新連接。這很是重要,例如,在設計將由許多應用程序使用的第三方 DLL 時。能夠經過添加附加的功能不斷地加強 DLL,同時確保現有應用程序繼續正常使用新的
DLL。MFC DLL 是用 .DEF 文件生成的。
編寫模塊定義語句的信息,請參見模塊定義語句的規則。有關序號導出的更多信息,請參見按序號而不是按名稱從 DLL 導出函數。
2. 使用 __declspec(dllexport) 的優缺點
使用 __declspec(dllexport) 很是方便,由於不須要考慮維護 .DEF 文件和獲取導出函數的修飾名。可是,沒法控制編譯器生成的導出序號。此方法適合某些狀況,例如,在設計要與控制的應用程序
一塊兒使用的 DLL 時;若是用新導出從新生成 DLL,則還須要從新生成應用程序。
/** 導出全局函數 */ DLL_API int global_fun(); /** 導出整個類 */ class DLL_API dllBase { public: dllBase ( void ); ~dllBase ( void ); int get(); private: static int base; list<string> m_list; }; /** 導出類的某個成員和方法 */ class dllBase { public: dllBase ( void ); ~dllBase ( void ); DLL_API int get(); private: DLL_API int base; list<string> m_list; };
利用VS工具目錄下的命令提示符進入相應DLL所在目錄,而後用dumpbin命令查看導出函數,或者使用view Dependencies工具直接打開dll文件咱們能夠看到如圖所示界面,界面介紹如圖所示
命令:..\Debug> dumpbin -exports dll.dll
注意到先前的函數名get變爲?get@dllBase@@QAEHXZ,所以這樣的函數只能在由相同編譯器編譯的程序中進行調用,而其餘程序調用則會出錯。之因此篡更名字是爲了C++的函數重載!
若要但願生成的函數名不變,則須要將以前#define DLL_API _declspec(dllexport)改變爲:
#define DLL_API extern "C" _declspec(dllexport)
該方法的缺陷:只能對全局函數,而不能正確地導出類的成員函數。若是咱們導出的函數的調用約定改變,即便咱們用了extern "C"作聲明,也會發生改編。
在工程中新建.def文件,在其中寫以下信息(其中get爲要導出的函數的名稱)
LIBRARY DLL //DLL表示dll文件名
EXPORTS //表示要導出那些函數
get
注意:由今生成的文件不會改變導出函數名
從應用程序調用動態連接庫的方法
一、將DLL文件夾DEBUG下生成的DLL文件,.lib文件拷貝到測試的應用程序(DLL_test)的工程目錄下
二、在應用程序的開發界面中,項目->DLL_test的屬性,在其屬性頁中,連接器->輸入->附加的依賴項中添加剛纔生成的DLL.lib導入文件。
三、在調用函數前,使用extern關鍵字聲明調用的函數是來自外部的程序或者使用_declspec(dllimport)關鍵字。如本程序中:
//方法1:告訴編譯器咱們所引用的符號是外部程序 //效率相對方法2低
extern int add(int a,int b);
extern int sub(int a,int b);
//方法2:告訴編譯器咱們所引用的符號是從.lib導入文件中導入的動態連接庫程序 //效率相對高
_declspec(dllimport) int add(int a,int b);
_declspec(dllimport) int sub(int a,int b);
//方法3:在設計DLL的時候就把方法2中的代碼添加到頭文件,而後在當前文件的#include中包含#include "..\DLL.h" //好處就是若是DLL文件和應用程序不是同一人編寫的時候能夠更好地調用!
#include "..\DLL.h"
//方法4:宏定義,這樣能夠用一個宏定義來完成_declspec(dllimport) _declspec(dllemport),並且可使該宏不只能夠給動態連接庫的開發方使用,也能夠給動態連接庫的調用方來使用
//在DLL.h(DLL設計文件)
#ifdef DLL_API
#else
#define DLL_API _declspec(dllimport)
#endif
咱們採用第四種辦法。
//動態調用動態連接庫 HINSTANCE hInst; hInst = LoadLibrary ( "Dll.dll" ); typedef int ( *ADDPROC ) ( int a, int b ); //若是在DLL設計過程當中是用_stdcall的,那這裏也要用_stdcall,不然系統將報錯! //typedef int (_stdcall *ADDPROC)(int a,int b); //"get"這裏爲用dumpbin –exports dll.dll中顯示的名字,若是爲?get@dllBase@@QAEHXZ, //那麼在這裏也應該寫ADDPROC Add=(ADDPROC)GetProcAddress(hInst," ?get@dllBase@@QAEHXZ "); ADDPROC Get = ( ADDPROC ) GetProcAddress ( hInst, "get" ); //利用序號來調用 //ADDPROC Get=(ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1)); if ( !Get ) { MessageBox ( "獲取函數地址失敗!" ); return; } int res = Get(); FreeLibrary ( hInst );