更新日志 —————————
2021/08/01 更新V2.2 增加 GetHmodule 函数 - 允许用户获取HMODULE以验证加载DLL是否成功。
2021/08/03 更新V2.3 增加 GetProcAddress_XXXX 函数 - 允许用户快捷地、在无需显式类型转换的情况下获取某个DLL函数的地址,以便为单个函数的频繁调用节省时间和代码,提高程序效率。
一直觉得用winapi动态调用dll很麻烦,所以干脆利用c++的模板函数等功能,写了一个类,用以快速调用DLL函数、快速获取函数地址。
此类的代码都在一个头文件中。当前版本有三大功能:调用DLL函数、快捷获取DLL函数地址、根据原函数信息生成C++方式修饰的函数名(即用C++方式导出到DLL中的函数名,类似这种:?XXX@YAHHJ@Z)。第二个功能尚不完善,目前不支持类成员函数名的生成、不支持参数中有结构体(struct)、枚举(enum)、引用(reference)(只是不支持生成名字,但支持调用,可以用dumpbin工具可获取dll中的函数列表)。
头文件完整代码:
/************************************ * Fast Dll Runner V2.3 for CPP11+ * * Author: szx0427 * * Date: 2021/08/03 * ************************************/ #pragma once //#include "pch.h" /* Uncomment this line if your project has a pch.h (VS2017+) */ //#include "stdafx.h" /* Uncomment this line if your project has a stdafx.h (Old version) */ #include <Windows.h> #include <tchar.h> #include <cassert> #include <string> #include <map> class CSzxRunDll2 { public: // Public data types enum CallingConventions { Stdcall, Cdecl, Fastcall }; protected: static const std::string m_Prefix[3]; static std::map<std::string, std::string> m_map; static void _InitMap() { if (m_map.size() > 0) return; m_map["void"] = "X"; m_map["char"] = "D"; m_map["unsigned char"] = "E"; m_map["short"] = "F"; m_map["unsigned short"] = "G"; m_map["int"] = "H"; m_map["unsigned int"] = "I"; m_map["long"] = "J"; m_map["unsigned long"] = "K"; m_map["__int64"] = "_J"; m_map["unsigned __int64"] = "_K"; m_map["float"] = "M"; m_map["double"] = "N"; m_map["bool"] = "_N"; m_map["*"] = "PA"; m_map["const *"] = "PB"; m_map["2 *"] = "0"; m_map["2 const *"] = "1"; } protected: // Protected fields and methods HMODULE m_hModule; template <class _ReturnType, class _FxnType, class... _ParamTypes> _ReturnType _basicCallDllFunc2(LPCSTR lpFxnName, const _ParamTypes&... args) { assert(m_hModule); assert(lpFxnName); _FxnType _Myfxn = (_FxnType)::GetProcAddress(m_hModule, lpFxnName); assert(_Myfxn); return _Myfxn(args...); } public: // Public methods CSzxRunDll2(LPCTSTR lpDllName = nullptr) : m_hModule(NULL) { if (lpDllName) m_hModule = LoadLibrary(lpDllName); _InitMap(); } virtual ~CSzxRunDll2() { UnloadDll(); } UINT LoadDll(LPCTSTR lpDllName) { assert(lpDllName); HMODULE hMod = LoadLibrary(lpDllName); if (hMod) { if (m_hModule) UnloadDll(); m_hModule = hMod; } return GetLastError(); } UINT UnloadDll() { FreeLibrary(m_hModule); return GetLastError(); } HMODULE GetHmodule() const // ++ v2.2 { return m_hModule; } template <class _ReturnType = void, class... _ParamTypes> _ReturnType CallDllFunc2_stdcall(LPCSTR lpFxnName, const _ParamTypes &..._Params) { return _basicCallDllFunc2<_ReturnType, _ReturnType(__stdcall*)(const _ParamTypes ...), _ParamTypes...> (lpFxnName, _Params...); } template <class _ReturnType = void, class... _ParamTypes> _ReturnType CallDllFunc2_cdecl(LPCSTR lpFxnName, const _ParamTypes &..._Params) { return _basicCallDllFunc2<_ReturnType, _ReturnType(__cdecl*)(const _ParamTypes ...), _ParamTypes...> (lpFxnName, _Params...); } template <class _ReturnType = void, class... _ParamTypes> _ReturnType CallDllFunc2_fastcall(LPCSTR lpFxnName, const _ParamTypes &..._Params) { return _basicCallDllFunc2<_ReturnType, _ReturnType(__fastcall*)(const _ParamTypes ...), _ParamTypes...> (lpFxnName, _Params...); } template <class _ReturnType = void, class... _ParamTypes> _ReturnType CallDllFunc2_thiscall(LPCSTR lpFxnName, const _ParamTypes &..._Params) { return _basicCallDllFunc2<_ReturnType, _ReturnType(__thiscall*)(const _ParamTypes ...), _ParamTypes...> (lpFxnName, _Params...); } // GetProcAddress_XXXX: ++v2.3 template <class _ReturnType = void, class... _ParamTypes> auto GetProcAddress_stdcall(LPCSTR lpProcName) { assert(m_hModule); return (_ReturnType(__stdcall*)(_ParamTypes...))::GetProcAddress(m_hModule, lpProcName); } template <class _ReturnType = void, class... _ParamTypes> auto GetProcAddress_cdecl(LPCSTR lpProcName) { assert(m_hModule); return (_ReturnType(__cdecl*)(_ParamTypes...))::GetProcAddress(m_hModule, lpProcName); } template <class _ReturnType = void, class... _ParamTypes> auto GetProcAddress_fastcall(LPCSTR lpProcName) { assert(m_hModule); return (_ReturnType(__fastcall*)(_ParamTypes...))::GetProcAddress(m_hModule, lpProcName); } template <class _ReturnType = void, class... _ParamTypes> auto GetProcAddress_thiscall(LPCSTR lpProcName) { assert(m_hModule); return (_ReturnType(__thiscall*)(_ParamTypes...))::GetProcAddress(m_hModule, lpProcName); } template <class _ReturnType = void, class... _ParamTypes> static std::string BuildCppDecoratedName(const std::string& sFxnName, CallingConventions cc = Cdecl) { _InitMap(); std::string ret = "?" + sFxnName + m_Prefix[(int)cc]; const char* szTypes[] = { typeid(_ReturnType).name(), typeid(_ParamTypes).name()... }; int nCount = 1 + sizeof...(_ParamTypes); std::string tmp, par; int pos1, pos2, sum = 0; for (int i = 0; i < nCount; i++) { tmp = szTypes[i]; // This version doesn't support struct/enum/reference assert(tmp.find("struct") == tmp.npos && tmp.find("enum") == tmp.npos && tmp.find("&") == tmp.npos); assert(tmp.find('[') == tmp.npos); // Array(x) Pointer(√) if ((pos1 = tmp.find(" *")) != tmp.npos) { if ((pos2 = tmp.find(" const *")) != tmp.npos) { if (i >= 1 && tmp == szTypes[i - 1]) par += m_map["2 const *"]; else par += m_map["const *"] + m_map[tmp.substr(0, pos2)]; } else { if (i >= 1 && tmp == szTypes[i - 1]) par += m_map["2 *"]; else par += m_map["*"] + m_map[tmp.substr(0, pos1)]; } } else par += m_map[tmp]; } if (par.length() == 1) par += "XZ"; else par += "@Z"; ret += par; return ret; } }; const std::string CSzxRunDll2::m_Prefix[3] = { "@@YG","@@YA","@@YI" }; std::map<std::string, std::string> CSzxRunDll2::m_map;
要使用此类, 只需要引入包含以上代码的头文件.
其中:
CSzxRunDll2 dll(TEXT("user32.dll")); int nRet = dll.CallDllFunc2_stdcall<int>("MessageBoxA", NULL, "Hello World!", "Title", MB_ICONINFORMATION);
GetProcAddress_XXXX可以快速获取当前类中某个DLL函数的地址。下划线后面的部分指定了调用约定(stdcall/cdecl/fastcall/thiscall). 这些函数模板的第一个模板参数为返回值类型,默认为void。后面的模板参数是函数从参数类型列表,如函数没有参数,则无需填写。实例化时,第一个函数参数时DLL函数的名称。该函数的返回值类型会自动根据模板参数计算,无需手动填写,代码中使用auto即可。下面是一个示例:
CSzxRunDll2 dll(TEXT("user32.dll")); auto MyMessageBox = dll.GetProcAddress_stdcall<int, HWND, LPCSTR, LPCSTR, UINT>("MessageBoxA"); MyMessageBox(NULL, "第一次使用MessageBox!", "11111", MB_OK); MyMessageBox(NULL, "第二次使用MessageBox!", "22222", MB_ICONASTERISK);
使用GetProcAddress_XXXX函数时请注意:由于本类的析构函数中会自动执行FreeLibrary函数释放DLL库,在一个类对象被析构后,使用它获取的函数地址将可能失效(大概率会失效)。所以,请保证一个对象获取的函数地址不会在它被析构后调用。下面是一个错误示范:
auto Fun1() { CSzxRunDll2 dll(TEXT("XXX.dll")); return dll.GetProcAddress_cdecl<int, int, int>("Add"); } void main() { auto fun = Fun1(); int n = fun(1, 2); // 可能崩溃 std::cout << n; }
此例中,Fun1函数中虽获取了Add函数的地址,但在Fun1函数返回之前,对象“dll”已执行了析构函数,它构造时加载的“XXX.dll”已被析构函数中执行的FreeLibrary释放,导致原有的函数地址失效。之所以说“可能崩溃”,是因为如果在一个类对象加载DLL前,就已加载过该DLL,则使用一次FreeLibrary不会将其释放,只会减少引用次数,所以原函数地址依然有效。如果要在多个函数内调用一个函数地址,请将CSzxRunDll2对象定义为全局变量或静态变量。
BuildCppDecoratedName函数BuildCppDecoratedName模板为静态成员函数模板, 作用是根据原函数的信息生成C++方式修饰过的函数名称. 有两种调用方法:
第一个模板参数为dll函数返回值类型, 后面的是dll函数参数类型列表. 实例化时, 第一个函数参数是dll函数名称, 第二个参数是调用约定, 可以为以下三个值中任意一个: CSzxRunDll2::Stdcall / CSzxRunDll2::Cdecl / CSzxRunDll2::Fastcall, 可不填, 默认为cdecl. 以下是一个使用该函数调用C++方式导出函数的例子:
CSzxRunDll2 dll(TEXT("math.dll")); // 要调用的函数原型: long __cdecl Add(int a, int b); std::string str = dll.BuildCppDecoratedName<long, int, int>("Add", CSzxRunDll2::Cdecl); std::cout << "Result: " << dll.CallDllFunc2_cdecl<long>(str.c_str(), 111, 22);
已知问题:
// 要调用的函数原型: void Add(const int &a, const int &b, long &result); int re; struct MyStruct { const int& a; const int& b; int& result; } param = { 222, 3000, re }; const char* name = "?Add@@YAXABH0AAJ@Z"; // 修饰过的函数名, 带引用的目前本类不支持生成,可使用dumpbin工具生成 dll.CallDllFunc2_cdecl(name, param);
此类还在继续开发完善中. 若有任何问题, 欢迎指正.
♥版权说明
本文由szx0427撰写,由Icys获得授权后在CnBlogs代为其发布发布。
知乎通道