更新日志 —————————

2021/08/01 更新V2.2 增加 GetHmodule 函数 - 允许用户获取HMODULE以验证加载DLL是否成功。

2021/08/03 更新V2.3 增加 GetProcAddress_XXXX 函数 - 允许用户快捷地、在无需显式类型转换的情况下获取某个DLL函数的地址,以便为单个函数的频繁调用节省时间和代码,提高程序效率。

一直觉得用winapi动态调用dll很麻烦,所以干脆利用c++的模板函数等功能,写了一个类,用以快速调用DLL函数、快速获取函数地址。

  此类的代码都在一个头文件中。当前版本有三大功能:调用DLL函数、快捷获取DLL函数地址、根据原函数信息生成C++方式修饰的函数名(即用C++方式导出到DLL中的函数名,类似这种:?XXX@YAHHJ@Z)。第二个功能尚不完善,目前不支持类成员函数名的生成、不支持参数中有结构体(struct)、枚举(enum)、引用(reference)(只是不支持生成名字,但支持调用,可以用dumpbin工具可获取dll中的函数列表)。

  头文件完整代码:

  1. /************************************
  2. * Fast Dll Runner V2.3 for CPP11+ *
  3. * Author: szx0427 *
  4. * Date: 2021/08/03 *
  5. ************************************/
  6. #pragma once
  7. //#include "pch.h" /* Uncomment this line if your project has a pch.h (VS2017+) */
  8. //#include "stdafx.h" /* Uncomment this line if your project has a stdafx.h (Old version) */
  9. #include <Windows.h>
  10. #include <tchar.h>
  11. #include <cassert>
  12. #include <string>
  13. #include <map>
  14. class CSzxRunDll2
  15. {
  16. public: // Public data types
  17. enum CallingConventions
  18. {
  19. Stdcall,
  20. Cdecl,
  21. Fastcall
  22. };
  23. protected:
  24. static const std::string m_Prefix[3];
  25. static std::map<std::string, std::string> m_map;
  26. static void _InitMap()
  27. {
  28. if (m_map.size() > 0)
  29. return;
  30. m_map["void"] = "X";
  31. m_map["char"] = "D";
  32. m_map["unsigned char"] = "E";
  33. m_map["short"] = "F";
  34. m_map["unsigned short"] = "G";
  35. m_map["int"] = "H";
  36. m_map["unsigned int"] = "I";
  37. m_map["long"] = "J";
  38. m_map["unsigned long"] = "K";
  39. m_map["__int64"] = "_J";
  40. m_map["unsigned __int64"] = "_K";
  41. m_map["float"] = "M";
  42. m_map["double"] = "N";
  43. m_map["bool"] = "_N";
  44. m_map["*"] = "PA";
  45. m_map["const *"] = "PB";
  46. m_map["2 *"] = "0";
  47. m_map["2 const *"] = "1";
  48. }
  49. protected: // Protected fields and methods
  50. HMODULE m_hModule;
  51. template <class _ReturnType, class _FxnType, class... _ParamTypes>
  52. _ReturnType _basicCallDllFunc2(LPCSTR lpFxnName, const _ParamTypes&... args)
  53. {
  54. assert(m_hModule);
  55. assert(lpFxnName);
  56. _FxnType _Myfxn = (_FxnType)::GetProcAddress(m_hModule, lpFxnName);
  57. assert(_Myfxn);
  58. return _Myfxn(args...);
  59. }
  60. public: // Public methods
  61. CSzxRunDll2(LPCTSTR lpDllName = nullptr)
  62. : m_hModule(NULL)
  63. {
  64. if (lpDllName)
  65. m_hModule = LoadLibrary(lpDllName);
  66. _InitMap();
  67. }
  68. virtual ~CSzxRunDll2()
  69. {
  70. UnloadDll();
  71. }
  72. UINT LoadDll(LPCTSTR lpDllName)
  73. {
  74. assert(lpDllName);
  75. HMODULE hMod = LoadLibrary(lpDllName);
  76. if (hMod)
  77. {
  78. if (m_hModule)
  79. UnloadDll();
  80. m_hModule = hMod;
  81. }
  82. return GetLastError();
  83. }
  84. UINT UnloadDll()
  85. {
  86. FreeLibrary(m_hModule);
  87. return GetLastError();
  88. }
  89. HMODULE GetHmodule() const // ++ v2.2
  90. { return m_hModule; }
  91. template <class _ReturnType = void, class... _ParamTypes>
  92. _ReturnType CallDllFunc2_stdcall(LPCSTR lpFxnName, const _ParamTypes &..._Params)
  93. {
  94. return _basicCallDllFunc2<_ReturnType, _ReturnType(__stdcall*)(const _ParamTypes ...), _ParamTypes...>
  95. (lpFxnName, _Params...);
  96. }
  97. template <class _ReturnType = void, class... _ParamTypes>
  98. _ReturnType CallDllFunc2_cdecl(LPCSTR lpFxnName, const _ParamTypes &..._Params)
  99. {
  100. return _basicCallDllFunc2<_ReturnType, _ReturnType(__cdecl*)(const _ParamTypes ...), _ParamTypes...>
  101. (lpFxnName, _Params...);
  102. }
  103. template <class _ReturnType = void, class... _ParamTypes>
  104. _ReturnType CallDllFunc2_fastcall(LPCSTR lpFxnName, const _ParamTypes &..._Params)
  105. {
  106. return _basicCallDllFunc2<_ReturnType, _ReturnType(__fastcall*)(const _ParamTypes ...), _ParamTypes...>
  107. (lpFxnName, _Params...);
  108. }
  109. template <class _ReturnType = void, class... _ParamTypes>
  110. _ReturnType CallDllFunc2_thiscall(LPCSTR lpFxnName, const _ParamTypes &..._Params)
  111. {
  112. return _basicCallDllFunc2<_ReturnType, _ReturnType(__thiscall*)(const _ParamTypes ...), _ParamTypes...>
  113. (lpFxnName, _Params...);
  114. }
  115. // GetProcAddress_XXXX: ++v2.3
  116. template <class _ReturnType = void, class... _ParamTypes>
  117. auto GetProcAddress_stdcall(LPCSTR lpProcName)
  118. {
  119. assert(m_hModule);
  120. return (_ReturnType(__stdcall*)(_ParamTypes...))::GetProcAddress(m_hModule, lpProcName);
  121. }
  122. template <class _ReturnType = void, class... _ParamTypes>
  123. auto GetProcAddress_cdecl(LPCSTR lpProcName)
  124. {
  125. assert(m_hModule);
  126. return (_ReturnType(__cdecl*)(_ParamTypes...))::GetProcAddress(m_hModule, lpProcName);
  127. }
  128. template <class _ReturnType = void, class... _ParamTypes>
  129. auto GetProcAddress_fastcall(LPCSTR lpProcName)
  130. {
  131. assert(m_hModule);
  132. return (_ReturnType(__fastcall*)(_ParamTypes...))::GetProcAddress(m_hModule, lpProcName);
  133. }
  134. template <class _ReturnType = void, class... _ParamTypes>
  135. auto GetProcAddress_thiscall(LPCSTR lpProcName)
  136. {
  137. assert(m_hModule);
  138. return (_ReturnType(__thiscall*)(_ParamTypes...))::GetProcAddress(m_hModule, lpProcName);
  139. }
  140. template <class _ReturnType = void, class... _ParamTypes>
  141. static std::string BuildCppDecoratedName(const std::string& sFxnName, CallingConventions cc = Cdecl)
  142. {
  143. _InitMap();
  144. std::string ret = "?" + sFxnName + m_Prefix[(int)cc];
  145. const char* szTypes[] = { typeid(_ReturnType).name(), typeid(_ParamTypes).name()... };
  146. int nCount = 1 + sizeof...(_ParamTypes);
  147. std::string tmp, par;
  148. int pos1, pos2, sum = 0;
  149. for (int i = 0; i < nCount; i++)
  150. {
  151. tmp = szTypes[i];
  152. // This version doesn't support struct/enum/reference
  153. assert(tmp.find("struct") == tmp.npos && tmp.find("enum") == tmp.npos && tmp.find("&") == tmp.npos);
  154. assert(tmp.find('[') == tmp.npos); // Array(x) Pointer(√)
  155. if ((pos1 = tmp.find(" *")) != tmp.npos)
  156. {
  157. if ((pos2 = tmp.find(" const *")) != tmp.npos)
  158. {
  159. if (i >= 1 && tmp == szTypes[i - 1])
  160. par += m_map["2 const *"];
  161. else
  162. par += m_map["const *"] + m_map[tmp.substr(0, pos2)];
  163. }
  164. else
  165. {
  166. if (i >= 1 && tmp == szTypes[i - 1])
  167. par += m_map["2 *"];
  168. else
  169. par += m_map["*"] + m_map[tmp.substr(0, pos1)];
  170. }
  171. }
  172. else
  173. par += m_map[tmp];
  174. }
  175. if (par.length() == 1)
  176. par += "XZ";
  177. else
  178. par += "@Z";
  179. ret += par;
  180. return ret;
  181. }
  182. };
  183. const std::string CSzxRunDll2::m_Prefix[3] = { "@@YG","@@YA","@@YI" };
  184. std::map<std::string, std::string> CSzxRunDll2::m_map;

要使用此类, 只需要引入包含以上代码的头文件.

其中:

  1. 构造函数和LoadDll函数可以加载一个DLL文件到类中.
  2. 析构函数和UnloadDll函数可以释放当前类中的DLL.
  3. GetHmodule函数(v2.2)可以获取当前类中的HMODULE,以验证加载DLL的操作是否成功。
  4. CallDllFunc2_XXXX可以快速调用当前类中DLL中的函数.下划线后面的部分指定了调用约定(stdcall/cdecl/fastcall/thiscall). 这些函数模板的第一个模板参数为返回值类型,可不填,默认为void. 后面的模板参数为dll函数参数类型列表,无需手动填入, 会根据具体函数参数自动识别. 实例化时,函数的第一个参数为DLL函数名称(注意,如果是C++方式导出的函数,需要填入修饰过的名称), 后面的参数是dll函数的参数列表(本函数模板使用了引用,保证不进行多余的数据复制浪费时间,数据直接传递给dll函数). C语言方式导出函数的调用示例:
  1. CSzxRunDll2 dll(TEXT("user32.dll"));
  2. int nRet = dll.CallDllFunc2_stdcall<int>("MessageBoxA", NULL, "Hello World!", "Title", MB_ICONINFORMATION);

GetProcAddress_XXXX可以快速获取当前类中某个DLL函数的地址。下划线后面的部分指定了调用约定(stdcall/cdecl/fastcall/thiscall). 这些函数模板的第一个模板参数为返回值类型,默认为void。后面的模板参数是函数从参数类型列表,如函数没有参数,则无需填写。实例化时,第一个函数参数时DLL函数的名称。该函数的返回值类型会自动根据模板参数计算,无需手动填写,代码中使用auto即可。下面是一个示例:

  1. CSzxRunDll2 dll(TEXT("user32.dll"));
  2. auto MyMessageBox = dll.GetProcAddress_stdcall<int, HWND, LPCSTR, LPCSTR, UINT>("MessageBoxA");
  3. MyMessageBox(NULL, "第一次使用MessageBox!", "11111", MB_OK);
  4. MyMessageBox(NULL, "第二次使用MessageBox!", "22222", MB_ICONASTERISK);

使用GetProcAddress_XXXX函数时请注意:由于本类的析构函数中会自动执行FreeLibrary函数释放DLL库,在一个类对象被析构后,使用它获取的函数地址将可能失效(大概率会失效)。所以,请保证一个对象获取的函数地址不会在它被析构后调用。下面是一个错误示范:

  1. auto Fun1()
  2. { CSzxRunDll2 dll(TEXT("XXX.dll")); return dll.GetProcAddress_cdecl<int, int, int>("Add"); }
  3. void main()
  4. {
  5. auto fun = Fun1();
  6. int n = fun(1, 2); // 可能崩溃
  7. std::cout << n;
  8. }

此例中,Fun1函数中虽获取了Add函数的地址,但在Fun1函数返回之前,对象“dll”已执行了析构函数,它构造时加载的“XXX.dll”已被析构函数中执行的FreeLibrary释放,导致原有的函数地址失效。之所以说“可能崩溃”,是因为如果在一个类对象加载DLL前,就已加载过该DLL,则使用一次FreeLibrary不会将其释放,只会减少引用次数,所以原函数地址依然有效。如果要在多个函数内调用一个函数地址,请将CSzxRunDll2对象定义为全局变量或静态变量。

BuildCppDecoratedName函数BuildCppDecoratedName模板为静态成员函数模板, 作用是根据原函数的信息生成C++方式修饰过的函数名称. 有两种调用方法:

  1. 通过对象调用: 类对象 . BuildCppDecoratedName<...>(...);
  2. 直接调用: CSzxRunDll2::BuildCppDecoratedName<...>(...);

第一个模板参数为dll函数返回值类型, 后面的是dll函数参数类型列表. 实例化时, 第一个函数参数是dll函数名称, 第二个参数是调用约定, 可以为以下三个值中任意一个: CSzxRunDll2::Stdcall / CSzxRunDll2::Cdecl / CSzxRunDll2::Fastcall, 可不填, 默认为cdecl. 以下是一个使用该函数调用C++方式导出函数的例子:

  1. CSzxRunDll2 dll(TEXT("math.dll"));
  2. // 要调用的函数原型: long __cdecl Add(int a, int b);
  3. std::string str = dll.BuildCppDecoratedName<long, int, int>("Add", CSzxRunDll2::Cdecl);
  4. std::cout << "Result: " << dll.CallDllFunc2_cdecl<long>(str.c_str(), 111, 22);

已知问题:

  1. BuildCppDecoratedName不支持DLL函数参数中有结构体(struct)、枚举(enum)、引用(reference).
  2. 在使用CallDllFunc2_XXX函数调用参数列表中带有引用(reference)的DLL函数时, 若按上述的常规方式会出现错误, 必须使用结构体传参, 如下所示:
  1. // 要调用的函数原型: void Add(const int &a, const int &b, long &result);
  2. int re;
  3. struct MyStruct
  4. {
  5. const int& a;
  6. const int& b;
  7. int& result;
  8. } param = { 222, 3000, re };
  9. const char* name = "?Add@@YAXABH0AAJ@Z"; // 修饰过的函数名, 带引用的目前本类不支持生成,可使用dumpbin工具生成
  10. dll.CallDllFunc2_cdecl(name, param);

此类还在继续开发完善中. 若有任何问题, 欢迎指正.

版权说明

本文由szx0427撰写,由Icys获得授权后在CnBlogs代为其发布发布。

知乎通道

C++利用模板在Windows上快速调用DLL函数的更多相关文章

  1. WAMP Server助你在Windows上快速搭建PHP集成环境

    WAMP Server助你在Windows上快速搭建PHP集成环境 原文地址 我想只要爬过几天网的同学都会知道PHP吧,异次元的新版本就是基于PHP的WordPress程序制造出来的,还有国内绝大部分 ...

  2. Windows上快速编译caffe CPU版本

    windows上快速安装配置Caffe的 cpu_only环境. 一:安装环境: 1.windows10: 2.Visual Studio2013: 3.Caffe版本:http://github.c ...

  3. 一个简洁通用的调用DLL函数的帮助类

    本次介绍一种调用dll函数的通用简洁的方法,消除了原来调用方式的重复与繁琐,使得我们调用dll函数的方式更加方便简洁.用过dll的人会发现c++中调用dll中的函数有点繁琐,调用过程是这样的:在加载d ...

  4. (原创)一个简洁通用的调用DLL函数的帮助类

    本次介绍一种调用dll函数的通用简洁的方法,消除了原来调用方式的重复与繁琐,使得我们调用dll函数的方式更加方便简洁.用过dll的人会发现c++中调用dll中的函数有点繁琐,调用过程是这样的:在加载d ...

  5. 在 C++Builder 工程里调用 DLL 函数

    调用 Visual C++ DLL 给 C++Builder 程序员提出了一些独特的挑战.在我们试图解决 Visual C++ 生成的 DLL 之前,回顾一下如何调用一个 C++Builder 创建的 ...

  6. ​c++ 调用DLL函数,出现错误

    ​c++ 调用DLL函数,出现错误 Run-Time Check Failure #0 - The value of ESP was not properly saved across a funct ...

  7. 动态调用DLL函数有时正常,有时报Access violation的异常

    动态调用DLL函数有时正常,有时报Access violation的异常 typedef int (add *)(int a,int b); void test() {     hInst=LoadL ...

  8. Python+CGI,在Windows上快速部署Python到IIS

    通过CGI,我们可以快速在Windows上部署Python 1. Windows安装IIS服务 2. 在IIS里打开“ISAPI和CGI限制”->添加,路径=python.exe的完全路径+&q ...

  9. 利用xshell从windows上传文件到虚拟机

    Xshell实现Windows上传文件到Linux主机 经常有这样的需求,我们在Windows下载的软件包,如何上传到远程Linux主机上?还有如何从Linux主机下载软件包到Windows下:之前我 ...

随机推荐

  1. 制作 Cocoapods 库

    一.准备工作:注册 trunk 1.更新 cocoapods 至最新版本 2.申请注册 trunk pod trunk register email 'name' 3.进入邮箱,点击激活注册 4.验证 ...

  2. CMD命令进入某个目录

    1.开始->运行->CMD 2.进入某个磁盘,直接盘符代号:如D:,不用CD 命令切换 3.进入除根录以下的文件夹 cd 文件夹路径 例如我要进入 E:/Program Files/PHP ...

  3. 使用CI/CD工具Github Action发布jar到Maven中央仓库

    之前发布开源项目Payment Spring Boot到Maven中央仓库我都是手动执行mvn deploy,在CI/CD大行其道的今天使用这种方式有点"原始".于是我一直在寻求一 ...

  4. Python 删除满足条件的某些行

    数据: data 字段:col 要删除的内容是 col == False 的行 # 方案一 data1 = data[~data['col'] == False] # ~ 取反 # 方案二 保留 da ...

  5. buu 达芬奇 && ROT

    一.达芬奇 百度了下电影简介,发现了斐波那契数列,同时发现密文是由斐波那契数列移动而来的,有点像base64变种 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 ...

  6. flask 的安装与使用

    一.Flask Flask 是一个轻量级的框架,可以将数据库中的数据呈现到页面上(动态网站). 之前搭建的网站是静态网站(没有连接数据库)页面的数据不会改变.但是现在用的网站都是动态网站. 1.下载F ...

  7. ESP32S2获取sht30温湿度

    static const char *TAG = "i2c-temp"; static unsigned char sht30_buf[6]={0}; static float g ...

  8. DIY一个智能开关kwswitch

    源码地址:https://gitee.com/kerwincui/kwswitch 平台简介 该智能开关平台包括服务端.硬件端.PC端和安卓端.硬件使用ESP8266模块,成本相对较低,可以发挥想象力 ...

  9. Python单元测试框架unittest之断言(assert)

    unittest中断言主要有三种类型: 1.基本的布尔断言,即:要么正确,要么错误的验证 2.比较断言,如比较两个变量的值(跟上面的布尔断言区别不大,主要是通过比较两个变量的值得出布尔值) 3.复杂断 ...

  10. 建立第一个SCRAPY的具体过程

    1.安装SCRAPY2.进入CMD:执行:SCRAPY显示: Scrapy 1.8.0 - no active project Usage: scrapy <command> [optio ...