更新日誌 —————————
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代為其釋出釋出。