一、 DLL的概念
能夠向程序提供一些函數、變量或類。程序員
靜態連接庫與動態連接庫的區別:
(1)靜態連接庫與動態連接庫都是共享代碼的方式。靜態連接庫把最後的指令都包含在最終生成的EXE文件中了;動態連接庫沒必要被包含在最終EXE文件中,EXE文件執行時能夠「動態」地引用和卸載這個與EXE獨立的DLL文件。
(2) 靜態連接庫中不能再包含其餘的動態連接庫或者靜態庫,而在動態連接庫中還能夠再包含其餘的動態或靜態連接庫。windows
DLL分類:
1。Non-MFC DLL(非MFC動態庫):不採用MFC類庫結構,其導出函數爲標準的C接口,能被非MFC或MFC編寫的應用程序所調用;函數
2。MFC Regular DLL(MFC規則DLL):非MFC動態庫MFC規則DLL 包含一個繼承自CWinApp的類,但其無消息循環;工具
3。MFC Extension DLL(MFC擴展DLL):採用MFC的動態連接版本建立,它只能被用MFC類庫所編寫的應用程序所調用。
spa
二、 建立一個DLL
2.1 非MFC的DLL
2.1.1聲明導出函數:
extern 「C」 __declspec(dllexport) int add(int a, int b);
其中 extern 「C」爲聲明爲C編譯。因爲C++編譯器在編譯的時候會形成其函數名的該變,在其餘應用程序中致使函數不可調用,而C編譯器則不會在編譯後改變其函數名。這樣若是用C編譯的程序來調用該dll中的函數時,可能會形成找不到該函數。
__declspec(dllexport)表示該函數爲DLL輸出函數,即其餘應用程序能夠調用該函數
從dll中聲明輸出函數有兩種方式:
(1) 另一種方式是採用模塊定義(.def) 文件聲明,.def文件爲連接器提供了有關被連接程序的導出、屬性及其餘方面的信息。
(2) 用__declspec(dllexport)來聲明函數
若是使用Visual C++來建立dll,對於一樣用VC建立的exe來講,調用dll沒有什麼問題。而若是用其餘工具來建立的exe來調用dll,就會出現問題。由於即便你不用C++編譯器,Microsoft C編譯器也會損害C函數。當用__stdcall將函數輸出時,C編譯器會將函數改成的形式。在這裏須要在.def文件中加入EXPORTS節來輸出函數:
EXPORTS
func
這樣,dll將用func函數名來輸出函數。
另外一種方式是用#pragma (linker, 「/exports:func=_func@1」),告訴編譯器輸出函數func,這種方式沒有前一種好。
若是經過VC++編寫的DLL欲被其餘語言編寫的程序調用,應將函數的調用方式聲明爲__stdcall方式,WINAPI都採用這種方式,而C/C++ 缺省的調用方式卻爲__cdecl。__stdcall方式與__cdecl對函數名最終生成符號的方式不一樣。若採用C編譯方式(在C++中需將函數聲明爲extern "C"),__stdcall調用約定在輸出函數名前面加下劃線,後面加「@」符號和參數的字節數,形如;而__cdecl調用約定僅在輸出函數名前面加下劃線,形如_functionname。
; lib.def : 導出DLL函數指針
LIBRARY dllTestEXPORTSadd @ 1.def文件的規則爲:
(1)LIBRARY語句說明.def文件相應的DLL;
(2)EXPORTS語句後列出要導出函數的名稱。能夠在.def文件中的導出函數名後加@n,表示要導出函數的序號爲n(在進行函數調用時,這個序號將發揮其做用);
(3).def 文件中的註釋由每一個註釋行開始處的分號 (;) 指定,且註釋不能與語句共享一行。
由此能夠看出,例子中lib.def文件的含義爲生成名爲「dllTest」的動態連接庫,導出其中的add函數,並指定add函數的序號爲1。繼承
2.1.2 Dll的調用方式:
DLL的調用分爲兩種方式:動態和靜態
接口
(1) 動態調用:進程
typedef int(*lpAddFun)(int, int); //宏定義函數指針類型
lpAddFun add;//函數指針
HINSTANCE hDll=LoadLibrary(「path」);
add=(lpAddFun)GetProcAddress(hDll, "add");//得到dll中的add函數指針
FreeLibrary(hDll);
在從dll調用中返回的函數、指針或者類都是以指針的方式,即返回的是函數、變量或類的地址。這裏必定要注意,不能簡單的用函數名來賦值。資源
(2) 靜態調用:
將生成的.dll和.lib文件拷入到調用dll的工程中,用命令
#pragma comment(lib,"dllTest.lib"),實現靜態調用,即把該dll在編譯的時候也編譯到exe文件中去,然後在工程中調用時用下面的代碼:
#pragma comment(lib,"dllTest.lib")
//.lib文件中僅僅是關於其對應DLL文件中函數的重定位信息extern "C" __declspec(dllimport) add(int x,int y);int main(int argc, char* argv[]){int result = add(2,3);printf("%d",result);return 0;} 由上述代碼能夠看出,靜態調用方式的順利進行須要完成兩個動做: (1)告訴編譯器與DLL相對應的.lib文件所在的路徑及文件名,#pragma comment(lib,"dllTest.lib")就是起這個做用。
程序員在創建一個DLL文件時,鏈接器會自動爲其生成一個對應的.lib文件,該文件包含了DLL 導出函數的符號名及序號(並不含有實際的代碼)。在應用程序裏,.lib文件將做爲DLL的替代文件參與編譯。
另一種顯式調用的方式是設置vc中的目錄和includefiles來實現
(2)聲明導入函數,extern "C" __declspec(dllimport) add(int x,int y)語句中的__declspec(dllimport)發揮這個做用。
靜態調用方式再也不須要使用系統API來加載、卸載DLL以及獲取DLL中導出函數的地址。這是由於,當程序員經過靜態連接方式編譯生成應用程序時,應用程序中調用的與.lib文件中導出符號相匹配的函數符號將進入到生成的EXE 文件中,.lib文件中所包含的與之對應的DLL文件的文件名也被編譯器存儲在 EXE文件內部。當應用程序運行過程當中須要加載DLL文件時,Windows將根據這些信息發現並加載DLL,而後經過符號名實現對DLL 函數的動態連接。這樣,EXE將能直接經過函數名調用DLL的輸出函數,就象調用程序內部的其餘函數同樣。
2.1.3 DllMain函數
Windows在加載dll的時候,會首先須要一個入口函數DllMain。當在dll中不定義DllMain的時候,windows會從其餘運行庫中調用一個不作任何操做的DllMain函數,直接返回true。DllMain是dll內部的函數,這意味着在調用dll的程序中不能顯式的調用。它是在 dll被調用時自動被調用的。
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call,
LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case: DLL_PROCESS_ATTACH:
break;
case: DLL_THREAD_ATTACH:
break;
case: DLL_THREAD_DETACH:
break;
case: DLL_PROCESS_DETACH:
break;
return TRUE;
}
}2.2 在dll中導出變量
一、在dll中定義變量 extern int global;
二、在.def中定義輸出 EXPORTS:
global DATA
三、 在應用程序中調用:#pragma comment(lib,"dllTest.lib")
extern int global;
注意在此引入的變量global,是一個地址,在使用時須要強制轉化爲指針後再用,才能獲得其值。
(int *)global=10;
在應用工程中引用DLL中全局變量的一個更好方法是:
extern int _declspec(dllimport) global; //用_declspec(dllimport)導入
經過_declspec(dllimport)方式導入的就是DLL中全局變量自己而再也不是其地址了,建議在一切可能的狀況下使用這種方式。
2.3 dll導出類
在定義的時候用 class _declspec(dllexport) classname{
}
在類中引用的時候用
加入類定義頭文件:#include 「classname.h」
Class _declspec(dllimport) classname 來導入類
三、 MFC規則Dll
MFC規則DLL的概念體如今兩方面:
(1) 它是MFC的
「是MFC的」意味着能夠在這種DLL的內部使用MFC;
(2) 它是規則的
「是規則的」意味着它不一樣於MFC擴展DLL,在MFC規則DLL的內部雖然可使用MFC,可是其與應用程序的接口不能是MFC。而MFC擴展DLL與應用程序的接口能夠是MFC,能夠從MFC擴展DLL中導出一個MFC類的派生類。
Regular DLL可以被全部支持DLL技術的語言所編寫的應用程序調用,固然也包括使用MFC的應用程序。在這種動態鏈接庫中,包含一個從CWinApp繼承下來的類,DllMain函數則由MFC自動提供。
(1)靜態連接到MFC 的規則DLL
靜態連接到MFC的規則DLL與MFC庫(包括MFC擴展 DLL)靜態連接,將MFC庫的代碼直接生成在.dll文件中。在調用這種DLL的接口時,MFC使用DLL的資源。所以,在靜態連接到MFC 的規則DLL中不須要進行模塊狀態的切換。
使用這種方法生成的規則DLL其程序較大,也可能包含重複的代碼。
(2)動態連接到MFC 的規則DLL
動態連接到MFC 的規則DLL 能夠和使用它的可執行文件同時動態連接到 MFC DLL 和任何MFC擴展 DLL。在使用了MFC共享庫的時候,默認狀況下,MFC使用主應用程序的資源句柄來加載資源模板。這樣,當DLL和應用程序中存在相同ID的資源時(即所謂的資源重複問題),系統可能不能得到正確的資源。所以,對於共享MFC DLL的規則DLL,咱們必須進行模塊切換以使得MFC可以找到正確的資源模板。
咱們能夠在Visual C++中設置MFC規則DLL是靜態連接到MFC DLL仍是動態連接到MFC DLL。如圖8,依次選擇Visual C++的project -> Settings -> General菜單或選項,在Microsoft Foundation Classes中進行設置。3.1規則DLL的建立;
與非MFCdll不一樣的是,在其定義裏面能夠引入MFC類,其餘與非MFC同樣
3.2規則DLL的調用
(1)顯示方式LoadLibrary , GetProcAdress , FreeLibrary
(2) 咱們照樣能夠在EXE程序中隱式調用MFC規則DLL,只須要將DLL工程生成的.lib文件和.dll文件拷入當前工程所在的目錄,並在RegularDllCallDlg.cpp文件(圖12所示對話框類的實現文件)的頂部添加:
#pragma comment(lib,"RegularDll.lib")
3.3共享MFC DLL的規則DLL的模塊切換
應用程序進程自己及其調用的每一個DLL模塊都具備一個全局惟一的HINSTANCE句柄,它們表明了DLL或EXE模塊在進程虛擬空間中的起始地址。進程自己的模塊句柄通常爲0x400000,而DLL模塊的缺省句柄爲0x10000000。若是程序同時加載了多個DLL,則每一個DLL模塊都會有不一樣的 HINSTANCE。應用程序在加載DLL時對其進行了重定位。
共享MFC DLL(或MFC擴展DLL)的規則DLL涉及到HINSTANCE句柄問題,HINSTANCE句柄對於加載資源特別重要。EXE和DLL都有其本身的資源,並且這些資源的ID可能重複,應用程序須要經過資源模塊的切換來找到正確的資源。若是應用程序須要來自於DLL的資源,就應將資源模塊句柄指定爲 DLL的模塊句柄;若是須要EXE文件中包含的資源,就應將資源模塊句柄指定爲EXE的模塊句柄。
模塊的切換有三種方式:
(1)在DLL函數中調用:AFX_MANAGE_STATE(AfxGetStaticModuleState());(推薦使用,最簡單)void ShowDlg(void){//方法1:在函數開始處變動,在函數結束時恢復//將AFX_MANAGE_STATE(AfxGetStaticModuleState());做爲接口函數的第一//條語句進行模塊狀態切換AFX_MANAGE_STATE(AfxGetStaticModuleState());
CDialog dlg(IDD_DLL_DIALOG);//打開ID爲2000的對話框dlg.DoModal();}(2)在DLL函數中調用AfxGetResourceHandle();AfxSetResourceHandle(HINSTANCE xxx);
(3)由應用程序自身切換(不推薦,最麻煩)
四、擴展MFCDLL
MFC擴展DLL的內涵爲MFC的擴展,用戶使用MFC擴展DLL就像使用MFC自己的DLL同樣。除了能夠在MFC擴展DLL的內部使用MFC之外, MFC擴展DLL與應用程序的接口部分也能夠是MFC。咱們通常使用MFC擴展DLL來包含一些MFC的加強功能,譬如擴展MFC的CStatic、 CButton等類使之具有更強大的能力。
導出一個類,直接在類聲明頭文件中使用AFX_EXT_CLASS便可,最後別忘了在調用dll的程序中加入class的頭文件五、總結:綜上所述:以上幾種dll主要由如下幾種區別:一、動態連接庫是將exe程序在程序執行的時候動態加載的,而靜態連接庫是在編譯的時 將其編譯在代碼之中的二、動態連接庫能夠輸出變量、函數和類。其中每種輸出的方式與調用方式不盡相同:(1)變量:在dll中定義 extern int global;在.def文件中輸出 EXPORTSglobal DATA或extern _declspec(dllexport)int global(不用輸出文件了)在程序中調用:靜態調用: #pragma comment(lib,"dllTest.lib")extern int _declspec(dllimport) global;動態調用:(2)函數:在dll中定義extern 「C」 __declspec(dllexport) int add(int a, int b);也能夠在.def文件中輸出該函數EXPORTS在程序中調用:靜態調用:#pragma comment(lib,"dllTest.lib")extern "C" __declspec(dllimport) add(int x,int y);動態調用:typedef int(*lpAddFun)(int, int); //宏定義函數指針類型
lpAddFun add;//函數指針
HINSTANCE hDll=LoadLibrary(「path」);
add=(lpAddFun)GetProcAddress(hDll, "add");//得到dll中的add函數指針
FreeLibrary(hDll);
在從dll調用中返回的函數、指針或者類都是以指針的方式會的,即返回的是函數、變量或類的地址。這裏必定要注意,不能簡單的用函數名來賦值。(3)類:在dll中定義:
在定義的時候用 class _declspec(dllexport) classname{
}
在類中引用的時候用
加入類定義頭文件:#include 「classname.h」
Class _declspec(dllimport) classname 來導入類
三、除了擴展MFC的dll外,其餘的dll都可被其餘語言編寫的應用程序來調用。