• 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
    "),TEXT("message"),MB_OK);
    16              //printf("dll process attach
    ");
    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 "..\Win32DllDinkWin32DllExtend.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是一个好办法.

  • 相关阅读:
    linux 系统账户 和 普通账户 的区别
    supervisor 相关命令
    linux 动态链接库查找方法;查找动态链接库位置; LIBRARY_PATH 和 LD_LIBRARY_PATH 的区别;LD_LIBRARY_PATH and LD_RUN_PATH的区别;MACOS 下查看动态链接库方法
    Ubuntu 一键伪装成Win 10,Kali Linux 2019 kali-undercover软件嫁接;Ubuntu 1804 使用 kali-undercover;
    C 实战练习题目30 – 回文数
    C 实战练习题目29
    C 实战练习题目28
    C 实战练习题目27
    C 实战练习题目26 -递归法求阶乘
    C 实战练习题目25
  • 原文地址:https://www.cnblogs.com/dengxiaojun/p/5331199.html
Copyright © 2020-2023  润新知