• VisualStudio 制作Dynamic Link Library动态链接库文件


    Dynamic Link Library动态链接库文件


    工具集

    借助工具可以获得Dll库函数的访问地址,以下推荐两款工具以供使用:

    如何生成

    __declspec(dllexport)

    将一个函数声名为导出函数,就是说这个函数要被其他程序调用,即作为DLL的一个对外函数接口。

    __declspec(dllexport) RETURN_TYPE FUNCTION()
    
    • 1

    extern “C”

    由于在制作DLL导出函数时由于C++存在函数重载

    • 因此__declspec(dllexport) FUNCTION(int,int)在DLL会被decorate,例如: 被decorate成为function_int_int,
    • 而且不同的编译器decorate的方法不同,造成了在用GetProcAddress取得FUNCTION地址时的不便
    • 使用extern "C"时,上述的decorate不会发生,因为C没有函数重载,如此一来被extern"C"修饰的函数,就不具备重载能力
    extern "C" {
      __declspec(dllexport) RETURN_TYPE FUNCTION(){
        ;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如何使用

    • 动态载入方式是指在编译之前并不知道将会调用哪些 DLL 函数, 完全是在运行过程中根据需要决定应调用哪些函数。
    • 方法是:用 LoadLibrary 函数加载动态链接库到内存,用 GetProcAddress函数动态获得 DLL 函数的入口地址。
    • 当一个 DLL 文件用 LoadLibrary 显式加载后,在任何时刻均可以通过调用 FreeLibrary 函数显式地从内存中把它给卸载。
    • 动态调用使用的 Windows API 函数主要有 3 个, 分别是 LoadLibraryGetProcAddressFreeLibrary

    声明调用

    注意DLL函数调用约定,必须一致

    1. __stdcall Windows API默认的函数调用协议
    2. __cdecl C/C++默认的函数调用协议
    3. __fastcall 适用于对性能要求较高的场合

    Example

    1. 假如我有一个函数接口如下:

      //@ GETCOMCHECKSUM_API是一个宏定义
      //@ #define GETCOMCHECKSUM_API __declspec(dllexport)
      GETCOMCHECKSUM_API int fnGetComCheckSum(
                              const unsigned char*  iCsArray,       //[In]数组
                              const unsigned int    iCsSize,        //[In]数值
                              unsigned char&        ioCsValue)      //[In/Out]数值
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    2. 那么我的调用应该这么写:

      //@ __cdecl * 后面函数名可以自定义
      typedef int(__cdecl *GetComCheckSum)(
                  unsigned char const *,
                  unsigned int,
                  unsigned char&);
      
      • 1
      • 2
      • 3
      • 4
      • 5

    LoadLibrary

    1. [格式] function LoadLibrary(LibFileName : PChar): Thandle;
    2. [功能] 加载由参数 LibFileName 指定的 DLL 文件
    3. [说明] 参数 LibFileName 指定了要装载的 DLL 文件名
      • 如果 LibFileName 没有包含一个路径,系统将按照:当前目录、Windows 目录、Windows 系统目录、包含当前任务可执行文件的目录、列在 PATH 环境变量中的目录等顺序查找文件。

    如果函数操作成功,将返回装载 DLL 库模块的实例句柄,否则,将返回一个错误代码,错误代码的定义如下表所示

    错误代码             含义
          0             系统内存不够,可执行文件被破坏或调用非法
          2             文件没有被发现
          3             路径没有被发现
          5             企图动态链接一个任务错误或者有一个共享或网络保护错误
          6             库需要为每个任务建立分离的数据段  
          8             没有足够的内存启动应用程序  
          10            Windows  版本不正确  
          11            可执行文件非法或不是Windows  应用程序,或在.  EXE映像中有错误  
          12            应用程序为一个不同的操作系统设计(如  OS/2)  
          13            应用程序为  MS  DOS   4. 0  设计  
          14            可执行文件的类型不知道  
          15            试图装载一个实模式应用程序(为早期Windows  版本设计)
          16            试图装载包含可写的多个数据段的可执行文件的第二个实例  
          19            试图装载一个压缩的可执行文件(文件必须被解压后才能被装载)  
          20            DLL  文件非法
          21            应用程序需要  32  位扩展
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    Example

    //@ 定义句柄
    HINSTANCE hSnKLib;
    
    //@ 获取链接库句柄       Getchecksum为dll的文件名 即 Getchecksum.dll
    //@ 系统将会在当前目录下寻找名为Getchecksum.dll的文件
    //@ 至于为什么使用_T("") ,_T是一个宏,作用是让你的程序支持Unicode编码,Windows使用两种字符集ANSI和UNICODE
    hSnKLib = LoadLibrary(_T("Getchecksum"))
    
    //@ 如果未能成功获取,抛出错误
    if (hSnKLib == NULL)
    {
        FreeLibrary(hSnKLib);
      	printf("LoadLibrary err\n");
      	getchar();
      	return 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    GetProcAddress

    1. 格式:function GetProcAddress(Module:Thandle; ProcName:PChar): TfarProc;
    2. 功能: 返回参数 Module 指定的模块中,由参数 ProcName 指定的过程或函数的入口地址
    3. 说明: 参数 Module 包含被调用函数的 DLL 句柄,这个值由 LoadLibrary 返回,procName是指向含有函数名的以 nil 结尾的字符串指针,或者可以是函数的次序值.
      • 大多数情况下,用函数名是一种更稳妥的选择。
      • 如果该函数执行成功,则返回 DLL 中由参数 ProcName 指定的过程或函数的入口地址,否则返回 nil 。

    Example

    //前面我们在头文件中声明了下述函数
    typedef int(__cdecl *GetComCheckSum)(
                unsigned char const *,
                unsigned int,
                unsigned char&);
    
    
    //实例化并且获取函数地址
    //fnGetComCheckSum为dll export出来的函数名,GetComCheckSum为我们引用时候声明的函数名
    //这里做的工作就是将dll中函数与我们声明的联系到一块。
    GetComCheckSum getcom = (GetComCheckSum)GetProcAddress(hSnKLib, "fnGetComCheckSum")
    
    if (!getcom)
    {
    	FreeLibrary(hSnKLib);			//释放dll文件
    	//Add your code here
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    FreeLibrary

    1. 格式:procedure FreeLibrary(Module: Thandle);
    2. 说明:将由参数 Module 指定的 DLL 文件从内存中卸载 1 次。
    3. 说明:Module 为 DLL 库的句柄。这个值由 LoadLibrary 返回。由于 DLL 在内存中只装载一次,因此调用 FreeLibrary 首先使 DLL 的引用计数减 1,如果计数减为 0 则卸载该 DLL
    4. 注意:每调用一次 LoadLibrary 函数就应调用一次 FreeLibrary 函数,以保证不会有多余的库模块在应用程序结束后仍留在内存中,否则导致内存泄漏。

    Example

    FreeLibrary(hSnKLib);
    
    • 1




    FAQS

    Question 1: GetLastError获取错误代码127

    问题描述:

    1. 采用"运行期间动态链接"自己的dll文件
    2. LoadLibrary成功获取dll模块句柄
    3. 但是GetProcAddress(hModule, “ExportFunc”)却返回NULL,GetLastError获取错误代码127,意思是“找不到指定程序”

    问题定位:

    1. 用Depends工具(VS2010默认没有,需另行下载:http://www.dependencywalker.com/),查看dll的导出函数名称。
    2. 发现导出函数名不再是“ExportFunc”,而根据函数的返回类型和参数进行了“decorate”,变为了“?ExportFunc@@YAXPB_W@Z”。

    解决方法两种:

    1. 修改GetProcAddress的第二个参数为真正的导出函数名称即可

    2. 在dll工程中添加DEF文件,写入如下内容:

      EXPORTS
                     ExportFunc
      
      • 1
      • 2
      • 重新编译dll工程。再次用Depends工具查看导出函数名称,即为“ExportFunc”。
      • 工程–链接器–输入 的模块定义文件中,将自己的DEF文件加上
        在这里插入图片描述




    参考案例

  • 相关阅读:
    CSDN每日一练 |『合并序列』『计数问题』『《教父》家族关系维护』2023-09-07
    java冒泡排序
    Java程序猿如何用Supplier来优化代码?
    【设计模式2_工厂、策略】
    tf.compat.v1.assign
    机器学习-朴素贝叶斯之多项式模型
    CefSharp进阶
    嵌入式工程师面试题
    Java InputStreamReader类的简介说明
    第八章 Spring AOP
  • 原文地址:https://blog.csdn.net/qq_33704787/article/details/126090055