WINDOWS動態連接庫技術能很好地實現代碼的分模塊,綜合來講,windows動態連接庫分爲三種WIN32動態連接庫,使用WINDOWS api函數調用設計,貼近底層,體積小,是最初Windows程序員最喜歡的技術之一,後來微軟推出了MFC類庫,因而動態連接庫進行了升級,多了兩種,第一種是非規則MFC類庫,這種類庫可以使用MFC的API進行程序設計,相對而言,比WIN32動態連接庫設計的時候簡便不少,同時還能實現資源與邏輯的分離,本地化等等特性,可是缺點就是每每須要帶有系統MFC庫編譯,效率下降了一些,不過這些年編程的趨勢就是如此,用速度換取開發效率嘛,第三種動態連接庫叫作MFC擴展類庫,這一類庫的主要功能是擴展MFC的控件,你們知道,MFC推出時附帶了一系列的控件,可是,這些控件相對來講,比較簡陋,咱們能夠在這些控件的基礎上擴展,實現一個咱們本身想要的控件,好比一個richtext,一個能夠編輯的list等,這種類庫控件收集多了,對程序員而言是寶貴的財富啊.ios
另外,動態連接庫的使用也包括兩種,一種叫作顯式調用,就是在程序中手動的用API裝載類庫,導入方法,這種用法又叫作動態調用,在這種調用中,只須要dll文件和程序員知道DLL文件中的方法名變量名就可使用,極爲方便和隱蔽.另外一種叫作隱式調用,編譯時就將整個目標dll裝入,不須要動態裝在,函數變量隨時能夠用,這種辦法編譯出來的程序體積略大,不過寫程序操心比較少,各有優劣.這種方法又叫作靜態調用,使用這種辦法,須要類庫開發者給出DLL對應的lib文件和對應的導出函數頭文件.程序員
如今先上代碼,看看如何編寫一個WIN32類庫編程
C文件以下windows
1 #include "DinkWin32DllExtend.h" 2 #include <iostream> 3 4 HANDLE hDllHandleLocal; 5 DWORD lastDllCallReason; 6 7 //第一個參數爲系統給的DLL的基地址 8 bool APIENTRY DllMain( HANDLE hDllHandle, DWORD dwReason, LPVOID lpreserved ) 9 { 10 hDllHandleLocal = hDllHandle; 11 lastDllCallReason = dwReason; 12 switch (dwReason) 13 { 14 case DLL_PROCESS_ATTACH: 15 MessageBox(NULL,TEXT("dll process attach\r\n"),TEXT("message"),MB_OK); 16 //printf("dll process attach\r\n"); 17 break; 18 case DLL_PROCESS_DETACH: 19 break; 20 case DLL_THREAD_ATTACH: 21 break; 22 case DLL_THREAD_DETACH: 23 break; 24 } 25 return TRUE; 26 } 27 28 HANDLE _stdcall GetDllHandle(void) 29 { 30 return hDllHandleLocal; 31 } 32 33 int DinkMath::Add(int x,int y) 34 { 35 return x+y; 36 }
1 #ifndef __DINK_WIN32_DLL_EXTEND_H_ 2 #define __DINK_WIN32_DLL_EXTEND_H_ 3 #include <windows.h> 4 5 #define DINK_WIN32_LIB_NAME "Win32Dll.dll" 6 7 /************************************************************************/ 8 /* WIN32DLL_EXPORTS是WIN32_DLL工程預約義的 */ 9 /* 宏定義,便於文件被定義和引用 */ 10 /* 主要用於靜態連接 */ 11 /************************************************************************/ 12 #ifndef WIN32DLL_EXPORTS 13 #define DINK_WIN32_DLL_FUNC __declspec(dllimport) 14 #define DINK_WIN32_DLL_VAR __declspec(dllimport) 15 #define DINK_WIN32_DLL_CLASS __declspec(dllimport) 16 #else 17 #define DINK_WIN32_DLL_FUNC __declspec(dllexport) 18 #define DINK_WIN32_DLL_VAR __declspec(dllexport) 19 #define DINK_WIN32_DLL_CLASS __declspec(dllexport) 20 #endif 21 22 /************************************************************************/ 23 /* 導出變量 */ 24 /************************************************************************/ 25 EXTERN_C DWORD DINK_WIN32_DLL_VAR lastDllCallReason; 26 27 /************************************************************************/ 28 /* 導出方法 */ 29 /************************************************************************/ 30 EXTERN_C HANDLE DINK_WIN32_DLL_FUNC GetDllHandle(void); 31 32 /************************************************************************/ 33 /* 注意,類不能動態加載,想要動態加載,使用COM */ 34 /************************************************************************/ 35 class DINK_WIN32_DLL_CLASS DinkMath//導出類 36 { 37 public: 38 int Add(int x,int y); 39 int myVar; 40 protected: 41 42 private: 43 44 }; 45 46 /************************************************************************/ 47 /*方法函數名:定義規則 DINK_DLL_FUNC_NAME_+函數名大寫 */ 48 /*方法導入規則:定義一個方法類型,直接能夠供外部程序使用 */ 49 /*typedef 返回值 (*函數名+DllCall)(參數列表) */ 50 /************************************************************************/ 51 #define DINK_DLL_FUNC_NAME_GETDLLHANDLE "GetDllHandle" 52 typedef HANDLE (*GetDllHandleDllCall)(void); 53 54 55 56 /*******************************************************************************/ 57 /* 變量名 定義規則 DINK_DLL_VAR_NAME_+變量名大寫 */ 58 /*定義一個宏轉換規則,直接將空指針轉換爲制定變量的操做空指針轉換爲對應指針的操做*/ 59 /*定義一個規則,直接將獲取到的指針轉換爲 */ 60 /*******************************************************************************/ 61 #define DINK_DLL_VAR_NAME_LASTDLLCALLREASON "lastDllCallReason" 62 #define DINK_MAKE_LASTDLLCALLREASON(ptr) (*((DWORD*)ptr)) 63 #define DINK_MAKE_LASTDLLCALLREASON_PTR(ptr) ((DWORD*)ptr) 64 65 66 #endif
上面的代碼有幾點須要講解.api
1.bool DllMain()函數是windows調用DLL時候的入口函數,DLL被裝載的時候自動調用該函數,函數的第一個參數是WINDOW調用這個DLL的時候給這個DLL的虛擬內存地址,經過這個地址能夠找到類庫,第二個參數爲系統調用DLL的緣由,包括是個選項,分別表明當一個進程的主線程調用和主線程卸載dll,一個進程中的非主線程調用或卸載DLL,宏定義的英文含義已經很明確,就不用多說了.安全
2.__declspec(dllexport) 關鍵字,表示導出一個變量,方法或者類函數
3.__declspec(dllimport)關鍵字,表示在一個項目中導入一個變量,方法,類spa
4.APIENTRY實際上表示_stdcall,這是windows函數調用的命名約定,表示調用這個函數的必須是windows API,與之對應的是_cdcel,表示調用這個函數的是一個C標準運行時.線程
5.在頭文件中經過宏定義的方法來指明導出和導入,這樣便於將這個頭文件同時用做dll編譯時的導出文件和外部調用時的導入文件,方便快捷,這是一個竅門.設計
6.在動態裝載庫的時候,咱們獲得的都是一個內存地址,無類型的,不管是變量仍是函數,因此最好是定義一套將空的地址轉換爲dll預約義的模塊的宏,別人使用起來還會很方便.
而後咱們先說靜態調用,靜態調用的代碼以下
1 #include <windows.h> 2 #include <iostream> 3 4 //靜態連接 5 #include "..\\Win32Dll\\DinkWin32DllExtend.h" 6 #pragma comment(lib,"Win32Dll.lib") 7 8 int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow) 9 { 10 MessageBox(NULL,TEXT("hello dll test"),TEXT("message"),MB_OK); 11 DWORD hdll = (DWORD)GetDllHandle(); 12 wchar_t* str = (wchar_t*)malloc(100); 13 swprintf_s (str,100,TEXT("hello dll address = %d"),hdll); 14 MessageBox(NULL,str,TEXT("message"),MB_OK); 15 swprintf_s (str,100,TEXT("last call reason = %d"),lastDllCallReason); 16 MessageBox(NULL,str,TEXT("message"),MB_OK); 17 DinkMath math; 18 swprintf_s (str,100,TEXT("dinkmath Add %d + %d = %d"),10,58,math.Add(10,58)); 19 MessageBox(NULL,str,TEXT("message"),MB_OK); 20 free((void*)str); 21 return -1; 22 }
這裏面有幾個要點,
1.包含文件的時候指明文件的相對路徑
2.要將編譯dll的時候對應的lib文件添加到咱們的工程中
3.#pragma關鍵字是靜態調用的時候很重要的關鍵字
4.由於咱們在dll中建立了類,調試模式下,當庫被釋放的時候,對應的內存釋放回引發CRT運行時錯誤,是堆棧不一致形成的,由於windows會爲每個DLL建立一個獨立的堆棧,這不用管,由於當你使用release編譯就不會報錯了,也能夠在調試中關掉CRT,不過不建議,否則之後出別的緣由引發的錯誤由於被關掉了沒法發現就划不來了.
5.使用unicode編程的時候,注意使用安全字符串函數.
靜態連接就說這麼說,你們看代碼領會精神,注意這個文件要和dll的.h文件一塊兒看哦,這樣容易記住要點.
接下來講說動態調用,其實也簡單,代碼以下
1 //動態連接 2 //獲取庫用loadlibrary 3 //釋放庫用freeLibrary 4 //獲取函數和變量用GetProcProcess 5 //dll句柄 HMODULE 6 #include "..\\Win32Dll\DinkWin32DllExtend.h" 7 8 int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd ) 9 { 10 HMODULE dinkDllLib; 11 GetDllHandleDllCall GetDllHandle; 12 DWORD* reason; 13 dinkDllLib = LoadLibrary(TEXT(DINK_WIN32_LIB_NAME)); 14 wchar_t* str = (wchar_t*)malloc(200); 15 if (dinkDllLib != NULL) 16 { 17 GetDllHandle = (GetDllHandleDllCall)GetProcAddress(dinkDllLib,DINK_DLL_FUNC_NAME_GETDLLHANDLE); 18 if(GetDllHandle != NULL) 19 { 20 swprintf_s(str,200,TEXT("GetDllHandle result is %d"),GetDllHandle()); 21 MessageBox(NULL,str,TEXT("message"),MB_OK); 22 reason = DINK_MAKE_LASTDLLCALLREASON_PTR(GetProcAddress(dinkDllLib,DINK_DLL_VAR_NAME_LASTDLLCALLREASON)); 23 if(reason != NULL) 24 { 25 swprintf_s(str,200,TEXT("last call reason is %d"),*reason); 26 MessageBox(NULL,str,TEXT("message"),MB_OK); 27 } 28 else 29 { 30 MessageBox(NULL,TEXT("var load failed"),TEXT("message"),MB_OK ); 31 return -2; 32 } 33 } 34 else 35 { 36 MessageBox(NULL,TEXT("function load failed"),TEXT("message"),MB_OK); 37 return -2; 38 } 39 FreeLibrary(dinkDllLib); 40 free(str); 41 } 42 else 43 { 44 MessageBox(NULL,TEXT("lib load failed"),TEXT("message"),MB_ICONERROR); 45 return -1; 46 } 47 return 0; 48 }
一樣有幾個要點
1.loadlibrary得到的就是dll模塊被程序裝載到內存中的地址,實際上能夠用一個三十二位的dword來表示,其window句柄類型爲hmodule,這個值在後面資源切換的時候其實蠻重要的,你們能夠當成這就是模塊地址吧.
2.GetProcAddress函數經過給定字符串參數獲取到一個指針,返回的指針是一個VOID類型的指針,因此須要咱們的強制轉換,不能直接訪問,切記切記.
3.調用dll完成之後若是不用dll了使用freeLibrary函數釋放DLL內存.
4.動態調用不能調用類,要是的類能被動態調用,使用靜態連接.
5.動態調用的時候要將DLL放在EXE問價能找到的地方,包括環境變量path路徑制定的區域,系統system區域,exe文件運行路徑這幾個.
以上就是WIN32動態連接庫的實際使用了,下面是一些補充,算是我看書記得筆記.
1.若是不想使用#pragma指令,那麼也能夠在vs工程的屬性中設置庫文件路徑可和庫文件名,那樣就能夠自動鏈接,記得OPENCV就是這麼幹的.
2.DllMain函數能夠作一些DLL內部的初始化工做,還能夠作一些資源的初始化,資源文件的讀取,釋放等工做.
3.DllMain不屬於導出函數,他屬於內部函數,不能導出,另外,當一個DLL源文件不包含DllMain時候,系統會自動爲DLL建立一個默認的空DllMain,相似於構造方法.
4.一個進程中調用的每個DDLL都有一個全局惟一的32字節的hmodule句柄,該句柄只能在特殊的函數內部使用,表明了Dll在進程虛擬空間中的起始地址,在WIN32總,HINSTANCE與HMODULE相同,二者能夠替換使用.
5.爲了更好地符合windows調用規則,在定義函數和導出函數的時候使用_stdcall是一個好辦法.