C++11实用技术(五)泛型编程加载dll接口函数

简介: C++11实用技术(五)泛型编程加载dll接口函数

C++11泛型编程简化加载dll代码

常见的加载dll方式:

HMODULE m_hDataModule;
m_hDataModule = LoadLibrary("myDll.dll");
typedef int (*PfunA)(int a, int b);//定义函数指针
PfunA fun = (PfunA)(GetProcAddress(m_hDataModule , "funA"));//加载接口
int ret = fun(1, 2);//执行函数

加载dll中的函数需要分为3步:

  • 1.定义函数指针
  • 2.从dll中加载接口
  • 3.执行函数

3步看起来不多,但是如果加载上百个这样的函数,那就非常繁琐,可能会遇到重复命名、参数定义不一致等各种问题。

C++11提供了方法可以通过泛型编程的方式,提供一个加载执行dll接口函数的通用函数。

示例代码如下:

std::map<string, FARPROC> _funcMap;
HMODULE _dataModule;
template <typename T>
std::function<T> LoadFunction(const string& functionName)
{
  auto it = _funcMap.find(functionName);
  if (it == _funcMap.end())
  {
    auto addr = GetProcAddress(_dataModule, functionName.c_str());
    if (!addr) return 
      nullptr;
    _funcMap.insert(std::make_pair(functionName, addr));
    it = _funcMap.find(functionName);
  }
  return std::function<T>((T*)(it->second));
}
template <typename T, typename... Args>
typename std::result_of<std::function<T>(Args...)>::type ExcuteFunc(const string& funcName, Args&&... args)
{
  auto func = LoadFunction<T>(funcName);
  if (func == nullptr)
  {
    std::cout << "Load " << funcName << " error!" << std::endl;
  }
  return func(std::forward<Args>(args)...);
}
int main()
{
  _dataModule = LoadLibrary("myDll.dll");
//一行代码即可加载并执行接口,并且支持各种类型的接口函数
  int funA = ExcuteFunc<int(int, int)>("funA", 1, 2);//有两个入参,返回int类型
  bool funB = ExcuteFunc<bool(int)>("funB", 1);//有一个入参,返回bool类型
  ExcuteFunc<void()>("funC");//没有入参,也没有返回值
  return 0;

上述加载dll过程分为两个函数:

  • 一个是LoadFunction函数,通过泛型编程提供加载dll中的函数指针。
  • 一个ExcuteFunc函数,用于执行加载后的函数。

LoadFunction

LoadFunction 函数用于加载函数指针。

它接受一个 functionName 参数,表示要加载的函数名。

首先,它在 _funcMap 中查找是否已经加载了该函数指针,如果找到了就直接返回。如果没有找到,则使用 GetProcAddress 函数从 _dataModule 中获取函数指针的地址,并将其插入到 _funcMap 中缓存起来。

最后,返回一个 std::function<T> 对象,其中 T 是函数指针的类型。

template <typename T>
std::function<T> LoadFunction(const string& functionName)
{
  auto it = _funcMap.find(functionName);
  if (it == _funcMap.end())
  {
    auto addr = GetProcAddress(_dataModule, functionName.c_str());
    if (!addr) return 
      nullptr;
    _funcMap.insert(std::make_pair(functionName, addr));
    it = _funcMap.find(functionName);
  }
  return std::function<T>((T*)(it->second));
}

注意使用

auto addr = GetProcAddress(_dataModule, functionName.c_str());

得到的addr类型只是一个输入参数为空,void*返回值类型的函数类型,所以说它是不能直接使用的。我们会在后面的代码中将加载的函数指针转换为对应的函数指针类型。如何去转换就是本文最核心的点


在函数最后返回值的时候

return std::function<T>((T*)(it->second));

作用是将 it->second(函数指针的地址)强制转换为类型为 T 的函数指针(T就是我们具体的函数指针类型),并使用 std::function 对象进行封装。

ExcuteFunc

ExcuteFunc 函数用于执行加载的函数。它接受一个 funcName 参数,表示要执行的函数名,以及可变参数 args,表示函数的实际参数。首先,它调用 LoadFunction 函数来加载函数指针。如果加载失败(即函数指针为空),则输出错误信息。然后,使用加载的函数指针 func 调用 std::function 对象,传递给定的参数,并返回执行结果。

template <typename T, typename... Args>
typename std::result_of<std::function<T>(Args...)>::type ExcuteFunc(const string& funcName, Args&&... args)
{
  auto func = LoadFunction<T>(funcName);
  if (func == nullptr)
  {
    std::cout << "Load " << funcName << " error!" << std::endl;
  }
  return func(std::forward<Args>(args)...);
}

函数中

typename std::result_of<std::function<T>(Args...)>::type ExcuteFunc(const string& funcName, Args&&... args)

用到一个traits技巧result_of,用来获取函数的返回值类型。

目录
相关文章
|
18天前
|
编译器 C++ 开发者
C++一分钟之-C++20新特性:模块化编程
【6月更文挑战第27天】C++20引入模块化编程,缓解`#include`带来的编译时间长和头文件管理难题。模块由接口(`.cppm`)和实现(`.cpp`)组成,使用`import`导入。常见问题包括兼容性、设计不当、暴露私有细节和编译器支持。避免这些问题需分阶段迁移、合理设计、明确接口和关注编译器更新。示例展示了模块定义和使用,提升代码组织和维护性。随着编译器支持加强,模块化将成为C++标准的关键特性。
48 3
|
18天前
|
存储 分布式数据库 API
技术好文:VisualC++查看文件被哪个进程占用
技术好文:VisualC++查看文件被哪个进程占用
|
12天前
|
存储 C++
【C++】string类的使用③(非成员函数重载Non-member function overloads)
这篇文章探讨了C++中`std::string`的`replace`和`swap`函数以及非成员函数重载。`replace`提供了多种方式替换字符串中的部分内容,包括使用字符串、子串、字符、字符数组和填充字符。`swap`函数用于交换两个`string`对象的内容,成员函数版本效率更高。非成员函数重载包括`operator+`实现字符串连接,关系运算符(如`==`, `&lt;`等)用于比较字符串,以及`swap`非成员函数。此外,还介绍了`getline`函数,用于按指定分隔符从输入流中读取字符串。文章强调了非成员函数在特定情况下的作用,并给出了多个示例代码。
|
12天前
|
存储 编译器 Linux
【C++】string类的使用②(容量接口Capacity )
这篇博客探讨了C++ STL中string的容量接口和元素访问方法。`size()`和`length()`函数等价,返回字符串的长度;`capacity()`提供已分配的字节数,可能大于长度;`max_size()`给出理论最大长度;`reserve()`预分配空间,不改变内容;`resize()`改变字符串长度,可指定填充字符。这些接口用于优化内存管理和适应字符串操作需求。
|
12天前
|
C++ 容器
【C++】string类的使用①(迭代器接口begin,end,rbegin和rend)
迭代器接口是获取容器元素指针的成员函数。`begin()`返回首元素的正向迭代器,`end()`返回末元素之后的位置。`rbegin()`和`rend()`提供反向迭代器,分别指向尾元素和首元素之前。C++11增加了const版本以供只读访问。示例代码展示了如何使用这些迭代器遍历字符串。
|
12天前
|
算法 C++ 容器
|
12天前
|
存储 编译器 程序员
|
13天前
|
存储 编译器 文件存储