如何动态获取dll中的导出函数地址(windows版本)
在使用第三方库的时候,一般会提供两种格式的库文件:静态库(lib)和动态库(dll)。在使用动态库的时候,需要这个dll和头文件外,还需要一个lib文件(该文件和静态lib不同,其只是包含了dll中导出函数的符号);但是如果只有这个dll库文件,我们该如何办呢?这就涉及到Windows动态加载相关知识,感兴趣的同学可以阅读《Windows核心编程》这本书,里面详细介绍了相关的知识点。下面主要结合github上一个开源项目进行介绍,了解和掌握如何用Windows API实现动态加载并且通过泛型实现不同导出函数地址的加载。
一、如何通过泛型编程动态获取指定dll导出函数地址
直接上代码,这里的源代码参考:not-enough-standards/shared_library.hpp at master · Alairion/not-enough-standards (github.com),我提取了其中Windows部分实现如下,并对相关泛型知识点做了备注。
1 #include <Windows.h> 2 #include <string> 3 #include <cassert> 4 5 class SharedLibrary 6 { 7 public: 8 using NativeHandleType = HMODULE; 9 10 public: 11 explicit SharedLibrary(const std::wstring& path) 12 { 13 _handle = ::LoadLibraryW(path.c_str()); 14 } 15 16 ~SharedLibrary() 17 { 18 if (_handle) { 19 ::FreeLibrary(_handle); 20 _handle = NULL; 21 } 22 } 23 24 SharedLibrary(const SharedLibrary&) = delete; 25 SharedLibrary& operator= (const SharedLibrary&) = delete; 26 27 SharedLibrary(SharedLibrary&& t) noexcept 28 { 29 _handle = std::exchange(t._handle, NativeHandleType{}); 30 } 31 32 33 SharedLibrary& operator= (SharedLibrary&& t) noexcept 34 { 35 _handle = std::exchange(t._handle, NativeHandleType{}); 36 37 return *this; 38 } 39 40 template<typename Func, 41 typename = std::enable_if_t<std::is_pointer_v<Func>&& std::is_function_v<std::remove_pointer_t<Func>>>> 42 // template< bool B, class T = void > 43 // using enable_if_t = typename enable_if<B, T>::type; 44 // 作用是在模版实例化的时候检测Func是否是指针类型并且去掉指针后释放满足函数定义 45 Func Load(const std::string& symbol) 46 { 47 // std::empty since c++17 48 // std::data since c++17 49 assert(!std::empty(symbol) && "load shared library symbol is empty."); 50 assert(_handle && "shared libray handle is invaild."); 51 auto unameFn = reinterpret_cast<void(*)()>(::GetProcAddress(_handle, std::data(symbol))); 52 return reinterpret_cast<Func>(unameFn); 53 } 54 55 template<typename Func, typename = std::enable_if_t<std::is_function_v<Func>>> 56 // template< bool B, class T = void > 57 // using enable_if_t = typename enable_if<B, T>::type; 58 // 作用是在模版实例化的时候检测Func是否满足函数的定义 59 Func* Load(const std::string& symbol) 60 { 61 return Load<Func*>(symbol); 62 } 63 64 private: 65 NativeHandleType _handle = NULL; 66 };
1 #include <iostream> 2 #include "shared_library.hpp" 3 4 int main() 5 { 6 constexpr auto lib = LR"(test.dll)"; 7 8 SharedLibrary sharedLib(lib); 9 10 auto fn = sharedLib.Load<int(int, int)>("Add"); 11 12 if (fn) { 13 std::cout << "fn = " << fn(1, 2) << std::endl; 14 } 15 16 return 0; 17 }
运行结果如下:
二、std::enable_if有什么作用
先来看看std::enable_if的原型:
1 template< bool B, class T = void > 2 struct enable_if;
如果B满足条件,那么enable_if类型的值就等于T,其中using enable_if_t = typename enable_if<B, T>::type。比如:template<typename Func, typename = std::enable_if_t<std::is_function_v<Func>>>在模版实例化Load<int(int, int)>后为Load<int(__cdecl*)(int, int)>。因为Func = int(int, int)使std::is_function_v<int(int, int)>为true,进而推导出std::enable_if_t<true, void>,那么std::enable_if_v = std::enable_if<>::type=void。那么template<typename Func,
typename = std::enable_if_t<std::is_pointer_v<Func>&& std::is_function_v<std::remove_pointer_t<Func>>>> 推导也就不难了。
其实我们可以通过栈帧信息加以验证的,上面调用demo验证截图如下:
参考源码仓库:not-enough-standards/shared_library.hpp at master · Alairion/not-enough-standards (github.com)