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,用来获取函数的返回值类型。