C++利用模板在Windows上快速調用DLL函數
更新日誌 —————————
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;
要使用此類, 只需要引入包含以上程式碼的頭文件.
其中:
- 構造函數和LoadDll函數可以載入一個DLL文件到類中.
- 析構函數和UnloadDll函數可以釋放當前類中的DLL.
- GetHmodule函數(v2.2)可以獲取當前類中的HMODULE,以驗證載入DLL的操作是否成功。
- CallDllFunc2_XXXX可以快速調用當前類中DLL中的函數.下劃線後面的部分指定了調用約定(stdcall/cdecl/fastcall/thiscall). 這些函數模板的第一個模板參數為返回值類型,可不填,默認為void. 後面的模板參數為dll函數參數類型列表,無需手動填入, 會根據具體函數參數自動識別. 實例化時,函數的第一個參數為DLL函數名稱(注意,如果是C++方式導出的函數,需要填入修飾過的名稱), 後面的參數是dll函數的參數列表(本函數模板使用了引用,保證不進行多餘的數據複製浪費時間,數據直接傳遞給dll函數). C語言方式導出函數的調用示例:
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++方式修飾過的函數名稱. 有兩種調用方法:
- 通過對象調用: 類對象 . BuildCppDecoratedName<…>(…);
- 直接調用: CSzxRunDll2::BuildCppDecoratedName<…>(…);
第一個模板參數為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);
已知問題:
- BuildCppDecoratedName不支援DLL函數參數中有結構體(struct)、枚舉(enum)、引用(reference).
- 在使用CallDllFunc2_XXX函數調用參數列表中帶有引用(reference)的DLL函數時, 若按上述的常規方式會出現錯誤, 必須使用結構體傳參, 如下所示:
// 要調用的函數原型: 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代為其發布發布。