WIN32動態鏈接庫設計與使用


  WINDOWS動態鏈接庫技術能很好地實現代碼的分模塊,綜合來說,windows動態鏈接庫分為三種WIN32動態鏈接庫,使用WINDOWS api函數調用設計,貼近底層,體積小,是最初Windows程序員最喜歡的技術之一,后來微軟推出了MFC類庫,於是動態鏈接庫進行了升級,多了兩種,第一種是非規則MFC類庫,這種類庫能夠使用MFC的API進行程序設計,相對而言,比WIN32動態鏈接庫設計的時候簡便很多,同時還能實現資源與邏輯的分離,本地化等等特性,但是缺點就是往往需要帶有系統MFC庫編譯,效率降低了一些,不過這些年編程的趨勢就是如此,用速度換取開發效率嘛,第三種動態鏈接庫叫做MFC擴展類庫,這一類庫的主要功能是擴展MFC的控件,大家知道,MFC推出時附帶了一系列的控件,但是,這些控件相對來說,比較簡陋,我們可以在這些控件的基礎上擴展,實現一個我們自己想要的控件,比如一個richtext,一個可以編輯的list等,這種類庫控件收集多了,對程序員而言是寶貴的財富啊.

  另外,動態鏈接庫的使用也包括兩種,一種叫做顯式調用,就是在程序中手動的用API裝載類庫,導入方法,這種用法又叫做動態調用,在這種調用中,只需要dll文件和程序員知道DLL文件中的方法名變量名就可以使用,極為方便和隱蔽.另一種叫做隱式調用,編譯時就將整個目標dll裝入,不需要動態裝在,函數變量隨時可以用,這種辦法編譯出來的程序體積略大,不過寫程序操心比較少,各有優劣.這種方法又叫做靜態調用,使用這種辦法,需要類庫開發者給出DLL對應的lib文件和對應的導出函數頭文件.

  現在先上代碼,看看如何編寫一個WIN32類庫

  C文件如下

  

 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

  上面的代碼有幾點需要講解.

  1.bool DllMain()函數是windows調用DLL時候的入口函數,DLL被裝載的時候自動調用該函數,函數的第一個參數是WINDOW調用這個DLL的時候給這個DLL的虛擬內存地址,通過這個地址可以找到類庫,第二個參數為系統調用DLL的原因,包括是個選項,分別代表當一個進程的主線程調用和主線程卸載dll,一個進程中的非主線程調用或卸載DLL,宏定義的英文含義已經很明確,就不用多說了.

  2.__declspec(dllexport) 關鍵字,表示導出一個變量,方法或者類

  3.__declspec(dllimport)關鍵字,表示在一個項目中導入一個變量,方法,類

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM