WIN32動態連接庫設計與使用

  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是一個好辦法.

相關文章
相關標籤/搜索