C++11实现一个加载dll并调用其中函数的dll帮助类
在C++中调用dll中的函数比较繁琐,调用过程如下:在加载dll后还需要定义一个对应的函数指针类型,接着调用GetProcAddress获取函数地址,再转成函数指针,最后调用该函数。如下:
-
void TestDll()
-
{
-
typedef int(*pMax)(int a, int b);
-
typedef int(*pGet)(int a);
-
-
HINSTANCE hDll = LoadLibraryA(
"mydll.dll");
-
if (hDll ==
nullptr)
-
return;
-
-
pMax Max = (pMax)GetProcAddress(hDll,
"Max");
-
if (Max ==
nullptr)
-
return;
-
-
int ret = Max(
5,
8);
-
-
pGet Get = (pGet)GetProcAddress(hDll,
"Get");
-
if (Get ==
nullptr)
-
return;
-
-
int ret = Get(
5);
-
-
FreeLibrary(hDll);
-
}
这段代码看起来很繁琐,因为每用一个函数就需要先定义一个函数指针,然后再根据名称获取函数地址,最后调用。如果一个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还有两个问题需解决:
- 函数的返回值可能是不同类型,如果以一种通用的返回值来消除这种不同返回值导致的差异呢?
- 函数的入参数目可能任意个数,且类型也不尽相同,如何来消除入参个数和类型的差异呢?
首先看一下如何封装GetProcAddress,将函数指针转换成std::function,代码如下:
-
template <
typename T>
-
std::function<T> GetFunction(const string& funcName)
-
{
-
FARPROC funAddress = GetProcAddress(m_hMod, funcName.c_str());
-
return
std::function<T>((T*)(funAddress));
-
}
其中T是std::function的模板参数,即函数类型的签名。如果要获取上面例子中的Max和Get函数,可以这样:
-
auto fmax = GetFunction<
int(
int,
int)>(
"Max");
-
auto fget = GetFunction<
int(
int)>(
"Get");
这种方式比之前先定义函数指针在调用GetProcAddress的方式更简洁通用。
再看看如何解决函数返回值和入参不统一的问题,通过result_of和可变参数模板来解决,最终的调用函数如下:
-
template <
typename T,
typename... Args>
-
typename
std::result_of<
std::function<T>(Args...)>::
type ExcecuteFunc(const string& funcName, Args&&... args)
-
{
-
return GetFunction<T>(funcName)(arg...);
-
}
上面的例子中要调用Max和Get函数,这样就行了:
-
auto max = ExecuteFunc<
int(
int,
int)>(
"Max",
5,
8);
-
auto ret = ExecuteFunc<
int(
int)>(
"Ret",
5);
比之前的调用方式简洁直观多了,没有了繁琐的函数指针的定义,没有了反复的调用GetProcAddress及其转换和调用。
完整代码如下:
-
#pragma once
-
#include <Windows.h>
-
#include <string>
-
#include <map>
-
#include <functional>
-
using
namespace
std;
-
-
class DllParser
-
{
-
public:
-
-
DllParser():m_hMod(
nullptr)
-
{
-
}
-
-
~DllParser()
-
{
-
UnLoad();
-
}
-
-
bool Load(const string& dllPath)
-
{
-
m_hMod = LoadLibraryA(dllPath.data());
-
if (
nullptr == m_hMod)
-
{
-
printf(
"LoadLibrary failed\n");
-
return
false;
-
}
-
-
return
true;
-
}
-
-
bool UnLoad()
-
{
-
if (m_hMod ==
nullptr)
-
return
true;
-
-
auto b = FreeLibrary(m_hMod);
-
if (!b)
-
return
false;
-
-
m_hMod =
nullptr;
-
return
true;
-
}
-
-
template <
typename T>
-
std::function<T> GetFunction(const string& funcName)
-
{
-
auto it = m_map.find(funcName);
-
if (it == m_map.end())
-
{
-
auto addr = GetProcAddress(m_hMod, funcName.c_str());
-
if (!addr)
-
return
nullptr;
-
m_map.insert(
std::
make_pair(funcName, addr));
-
it = m_map.find(funcName);
-
}
-
-
return
std::function<T>((T*) (it->second));
-
}
-
-
template <
typename T,
typename... Args>
-
typename
std::result_of<
std::function<T>(Args...)>::
type ExcecuteFunc(const string& funcName, Args&&... args)
-
{
-
auto f = GetFunction<T>(funcName);
-
if (f ==
nullptr)
-
{
-
string s =
"can not find this function " + funcName;
-
throw
std::exception(s.c_str());
-
}
-
-
return f(
std::forward<Args>(args)...);
-
}
-
-
private:
-
HMODULE m_hMod;
-
std::
map<
string, FARPROC> m_map;
-
};
实现的关键是如何将一个FARPROC变成一个函数指针复制给std::function,然后再调用可变参数执行。函数的返回值通过std::result_of来泛化,使得不同的返回值的dll函数都可以用相同的方式来调用。
参考资料: