C++11实现一个加载dll并调用其中函数的dll帮助类

动态库 专栏收录该内容
7 篇文章 0 订阅

在C++中调用dll中的函数比较繁琐,调用过程如下:在加载dll后还需要定义一个对应的函数指针类型,接着调用GetProcAddress获取函数地址,再转成函数指针,最后调用该函数。如下:


 
  1. void TestDll()
  2. {
  3. typedef int(*pMax)(int a, int b);
  4. typedef int(*pGet)(int a);
  5. HINSTANCE hDll = LoadLibraryA( "mydll.dll");
  6. if (hDll == nullptr)
  7. return;
  8. pMax Max = (pMax)GetProcAddress(hDll, "Max");
  9. if (Max == nullptr)
  10. return;
  11. int ret = Max( 5, 8);
  12. pGet Get = (pGet)GetProcAddress(hDll, "Get");
  13. if (Get == nullptr)
  14. return;
  15. int ret = Get( 5);
  16. FreeLibrary(hDll);
  17. }

这段代码看起来很繁琐,因为每用一个函数就需要先定义一个函数指针,然后再根据名称获取函数地址,最后调用。如果一个dll中有上百个函数,这种繁琐的定义会让人不胜其烦。其实获取函数地址和调用函数的过程是重复逻辑,应该消除,我们不希望每次都定义一个函数指针和调用GetProcAddress,应该用一种简洁通用的方式去调用dll中的函数。我们希望调用dll中的函数就像调用普通的函数一样,传入一个函数名称和函数的参数就可以实现函数的调用,类似于:

Ret CallDllFunc(const string& funName, T arg);
 

如果以这种方式调用,就能避免繁琐的函数指针定义以及反复地调用GetProcAddress了。

下面介绍一种可行的解决方案,如果按照Ret CallDllFunc(const string& funName, T arg);这种方式调用,首先要把函数指针转换成一种函数对象或泛型函数,这里可以用std::function去做这个事情,即通过一个函数封装GetProcAddress,这样通过函数名称就能获取一个泛型函数std::function,希望这个function是通用的,不论dll中是什么函数都可以转换成这个std::function,最后调用这个通用的function即可。但是调用这个通用的function还有两个问题需解决:

  1. 函数的返回值可能是不同类型,如果以一种通用的返回值来消除这种不同返回值导致的差异呢?
  2. 函数的入参数目可能任意个数,且类型也不尽相同,如何来消除入参个数和类型的差异呢?

首先看一下如何封装GetProcAddress,将函数指针转换成std::function,代码如下:


 
  1. template < typename T>
  2. std::function<T> GetFunction(const string& funcName)
  3. {
  4. FARPROC funAddress = GetProcAddress(m_hMod, funcName.c_str());
  5. return std::function<T>((T*)(funAddress));
  6. }

其中T是std::function的模板参数,即函数类型的签名。如果要获取上面例子中的Max和Get函数,可以这样:


 
  1. auto fmax = GetFunction< int( int, int)>( "Max");
  2. auto fget = GetFunction< int( int)>( "Get");

这种方式比之前先定义函数指针在调用GetProcAddress的方式更简洁通用。

再看看如何解决函数返回值和入参不统一的问题,通过result_of和可变参数模板来解决,最终的调用函数如下:


 
  1. template < typename T, typename... Args>
  2. typename std::result_of< std::function<T>(Args...)>:: type ExcecuteFunc(const string& funcName, Args&&... args)
  3. {
  4. return GetFunction<T>(funcName)(arg...);
  5. }

上面的例子中要调用Max和Get函数,这样就行了:


 
  1. auto max = ExecuteFunc< int( int, int)>( "Max", 5, 8);
  2. auto ret = ExecuteFunc< int( int)>( "Ret", 5);

比之前的调用方式简洁直观多了,没有了繁琐的函数指针的定义,没有了反复的调用GetProcAddress及其转换和调用。

完整代码如下:


 
  1. #pragma once
  2. #include <Windows.h>
  3. #include <string>
  4. #include <map>
  5. #include <functional>
  6. using namespace std;
  7. class DllParser
  8. {
  9. public:
  10. DllParser():m_hMod( nullptr)
  11. {
  12. }
  13. ~DllParser()
  14. {
  15. UnLoad();
  16. }
  17. bool Load(const string& dllPath)
  18. {
  19. m_hMod = LoadLibraryA(dllPath.data());
  20. if ( nullptr == m_hMod)
  21. {
  22. printf( "LoadLibrary failed\n");
  23. return false;
  24. }
  25. return true;
  26. }
  27. bool UnLoad()
  28. {
  29. if (m_hMod == nullptr)
  30. return true;
  31. auto b = FreeLibrary(m_hMod);
  32. if (!b)
  33. return false;
  34. m_hMod = nullptr;
  35. return true;
  36. }
  37. template < typename T>
  38. std::function<T> GetFunction(const string& funcName)
  39. {
  40. auto it = m_map.find(funcName);
  41. if (it == m_map.end())
  42. {
  43. auto addr = GetProcAddress(m_hMod, funcName.c_str());
  44. if (!addr)
  45. return nullptr;
  46. m_map.insert( std:: make_pair(funcName, addr));
  47. it = m_map.find(funcName);
  48. }
  49. return std::function<T>((T*) (it->second));
  50. }
  51. template < typename T, typename... Args>
  52. typename std::result_of< std::function<T>(Args...)>:: type ExcecuteFunc(const string& funcName, Args&&... args)
  53. {
  54. auto f = GetFunction<T>(funcName);
  55. if (f == nullptr)
  56. {
  57. string s = "can not find this function " + funcName;
  58. throw std::exception(s.c_str());
  59. }
  60. return f( std::forward<Args>(args)...);
  61. }
  62. private:
  63. HMODULE m_hMod;
  64. std:: map< string, FARPROC> m_map;
  65. };

实现的关键是如何将一个FARPROC变成一个函数指针复制给std::function,然后再调用可变参数执行。函数的返回值通过std::result_of来泛化,使得不同的返回值的dll函数都可以用相同的方式来调用。

 

参考资料:

深入应用C++11:代码优化与工程级应用

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值