更新日誌 —————————

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中的函式列表)。

  標頭檔案完整程式碼:

  1. /************************************
  2. * Fast Dll Runner V2.3 for CPP11+ *
  3. * Author: szx0427 *
  4. * Date: 2021/08/03 *
  5. ************************************/
  6. #pragma once
  7. //#include "pch.h" /* Uncomment this line if your project has a pch.h (VS2017+) */
  8. //#include "stdafx.h" /* Uncomment this line if your project has a stdafx.h (Old version) */
  9. #include <Windows.h>
  10. #include <tchar.h>
  11. #include <cassert>
  12. #include <string>
  13. #include <map>
  14. class CSzxRunDll2
  15. {
  16. public: // Public data types
  17. enum CallingConventions
  18. {
  19. Stdcall,
  20. Cdecl,
  21. Fastcall
  22. };
  23. protected:
  24. static const std::string m_Prefix[3];
  25. static std::map<std::string, std::string> m_map;
  26. static void _InitMap()
  27. {
  28. if (m_map.size() > 0)
  29. return;
  30. m_map["void"] = "X";
  31. m_map["char"] = "D";
  32. m_map["unsigned char"] = "E";
  33. m_map["short"] = "F";
  34. m_map["unsigned short"] = "G";
  35. m_map["int"] = "H";
  36. m_map["unsigned int"] = "I";
  37. m_map["long"] = "J";
  38. m_map["unsigned long"] = "K";
  39. m_map["__int64"] = "_J";
  40. m_map["unsigned __int64"] = "_K";
  41. m_map["float"] = "M";
  42. m_map["double"] = "N";
  43. m_map["bool"] = "_N";
  44. m_map["*"] = "PA";
  45. m_map["const *"] = "PB";
  46. m_map["2 *"] = "0";
  47. m_map["2 const *"] = "1";
  48. }
  49. protected: // Protected fields and methods
  50. HMODULE m_hModule;
  51. template <class _ReturnType, class _FxnType, class... _ParamTypes>
  52. _ReturnType _basicCallDllFunc2(LPCSTR lpFxnName, const _ParamTypes&... args)
  53. {
  54. assert(m_hModule);
  55. assert(lpFxnName);
  56. _FxnType _Myfxn = (_FxnType)::GetProcAddress(m_hModule, lpFxnName);
  57. assert(_Myfxn);
  58. return _Myfxn(args...);
  59. }
  60. public: // Public methods
  61. CSzxRunDll2(LPCTSTR lpDllName = nullptr)
  62. : m_hModule(NULL)
  63. {
  64. if (lpDllName)
  65. m_hModule = LoadLibrary(lpDllName);
  66. _InitMap();
  67. }
  68. virtual ~CSzxRunDll2()
  69. {
  70. UnloadDll();
  71. }
  72. UINT LoadDll(LPCTSTR lpDllName)
  73. {
  74. assert(lpDllName);
  75. HMODULE hMod = LoadLibrary(lpDllName);
  76. if (hMod)
  77. {
  78. if (m_hModule)
  79. UnloadDll();
  80. m_hModule = hMod;
  81. }
  82. return GetLastError();
  83. }
  84. UINT UnloadDll()
  85. {
  86. FreeLibrary(m_hModule);
  87. return GetLastError();
  88. }
  89. HMODULE GetHmodule() const // ++ v2.2
  90. { return m_hModule; }
  91. template <class _ReturnType = void, class... _ParamTypes>
  92. _ReturnType CallDllFunc2_stdcall(LPCSTR lpFxnName, const _ParamTypes &..._Params)
  93. {
  94. return _basicCallDllFunc2<_ReturnType, _ReturnType(__stdcall*)(const _ParamTypes ...), _ParamTypes...>
  95. (lpFxnName, _Params...);
  96. }
  97. template <class _ReturnType = void, class... _ParamTypes>
  98. _ReturnType CallDllFunc2_cdecl(LPCSTR lpFxnName, const _ParamTypes &..._Params)
  99. {
  100. return _basicCallDllFunc2<_ReturnType, _ReturnType(__cdecl*)(const _ParamTypes ...), _ParamTypes...>
  101. (lpFxnName, _Params...);
  102. }
  103. template <class _ReturnType = void, class... _ParamTypes>
  104. _ReturnType CallDllFunc2_fastcall(LPCSTR lpFxnName, const _ParamTypes &..._Params)
  105. {
  106. return _basicCallDllFunc2<_ReturnType, _ReturnType(__fastcall*)(const _ParamTypes ...), _ParamTypes...>
  107. (lpFxnName, _Params...);
  108. }
  109. template <class _ReturnType = void, class... _ParamTypes>
  110. _ReturnType CallDllFunc2_thiscall(LPCSTR lpFxnName, const _ParamTypes &..._Params)
  111. {
  112. return _basicCallDllFunc2<_ReturnType, _ReturnType(__thiscall*)(const _ParamTypes ...), _ParamTypes...>
  113. (lpFxnName, _Params...);
  114. }
  115. // GetProcAddress_XXXX: ++v2.3
  116. template <class _ReturnType = void, class... _ParamTypes>
  117. auto GetProcAddress_stdcall(LPCSTR lpProcName)
  118. {
  119. assert(m_hModule);
  120. return (_ReturnType(__stdcall*)(_ParamTypes...))::GetProcAddress(m_hModule, lpProcName);
  121. }
  122. template <class _ReturnType = void, class... _ParamTypes>
  123. auto GetProcAddress_cdecl(LPCSTR lpProcName)
  124. {
  125. assert(m_hModule);
  126. return (_ReturnType(__cdecl*)(_ParamTypes...))::GetProcAddress(m_hModule, lpProcName);
  127. }
  128. template <class _ReturnType = void, class... _ParamTypes>
  129. auto GetProcAddress_fastcall(LPCSTR lpProcName)
  130. {
  131. assert(m_hModule);
  132. return (_ReturnType(__fastcall*)(_ParamTypes...))::GetProcAddress(m_hModule, lpProcName);
  133. }
  134. template <class _ReturnType = void, class... _ParamTypes>
  135. auto GetProcAddress_thiscall(LPCSTR lpProcName)
  136. {
  137. assert(m_hModule);
  138. return (_ReturnType(__thiscall*)(_ParamTypes...))::GetProcAddress(m_hModule, lpProcName);
  139. }
  140. template <class _ReturnType = void, class... _ParamTypes>
  141. static std::string BuildCppDecoratedName(const std::string& sFxnName, CallingConventions cc = Cdecl)
  142. {
  143. _InitMap();
  144. std::string ret = "?" + sFxnName + m_Prefix[(int)cc];
  145. const char* szTypes[] = { typeid(_ReturnType).name(), typeid(_ParamTypes).name()... };
  146. int nCount = 1 + sizeof...(_ParamTypes);
  147. std::string tmp, par;
  148. int pos1, pos2, sum = 0;
  149. for (int i = 0; i < nCount; i++)
  150. {
  151. tmp = szTypes[i];
  152. // This version doesn't support struct/enum/reference
  153. assert(tmp.find("struct") == tmp.npos && tmp.find("enum") == tmp.npos && tmp.find("&") == tmp.npos);
  154. assert(tmp.find('[') == tmp.npos); // Array(x) Pointer(√)
  155. if ((pos1 = tmp.find(" *")) != tmp.npos)
  156. {
  157. if ((pos2 = tmp.find(" const *")) != tmp.npos)
  158. {
  159. if (i >= 1 && tmp == szTypes[i - 1])
  160. par += m_map["2 const *"];
  161. else
  162. par += m_map["const *"] + m_map[tmp.substr(0, pos2)];
  163. }
  164. else
  165. {
  166. if (i >= 1 && tmp == szTypes[i - 1])
  167. par += m_map["2 *"];
  168. else
  169. par += m_map["*"] + m_map[tmp.substr(0, pos1)];
  170. }
  171. }
  172. else
  173. par += m_map[tmp];
  174. }
  175. if (par.length() == 1)
  176. par += "XZ";
  177. else
  178. par += "@Z";
  179. ret += par;
  180. return ret;
  181. }
  182. };
  183. const std::string CSzxRunDll2::m_Prefix[3] = { "@@YG","@@YA","@@YI" };
  184. std::map<std::string, std::string> CSzxRunDll2::m_map;

要使用此類, 只需要引入包含以上程式碼的標頭檔案.

其中:

  1. 建構函式和LoadDll函式可以載入一個DLL檔案到類中.
  2. 解構函式和UnloadDll函式可以釋放當前類中的DLL.
  3. GetHmodule函式(v2.2)可以獲取當前類中的HMODULE,以驗證載入DLL的操作是否成功。
  4. CallDllFunc2_XXXX可以快速呼叫當前類中DLL中的函式.下劃線後面的部分指定了呼叫約定(stdcall/cdecl/fastcall/thiscall). 這些函式模板的第一個模板引數為返回值型別,可不填,預設為void. 後面的模板引數為dll函式引數型別列表,無需手動填入, 會根據具體函式引數自動識別. 例項化時,函式的第一個引數為DLL函式名稱(注意,如果是C++方式匯出的函式,需要填入修飾過的名稱), 後面的引數是dll函式的引數列表(本函式模板使用了引用,保證不進行多餘的資料複製浪費時間,資料直接傳遞給dll函式). C語言方式匯出函式的呼叫示例:
  1. CSzxRunDll2 dll(TEXT("user32.dll"));
  2. int nRet = dll.CallDllFunc2_stdcall<int>("MessageBoxA", NULL, "Hello World!", "Title", MB_ICONINFORMATION);

GetProcAddress_XXXX可以快速獲取當前類中某個DLL函式的地址。下劃線後面的部分指定了呼叫約定(stdcall/cdecl/fastcall/thiscall). 這些函式模板的第一個模板引數為返回值型別,預設為void。後面的模板引數是函式從引數型別列表,如函式沒有引數,則無需填寫。例項化時,第一個函式引數時DLL函式的名稱。該函式的返回值型別會自動根據模板引數計算,無需手動填寫,程式碼中使用auto即可。下面是一個示例:

  1. CSzxRunDll2 dll(TEXT("user32.dll"));
  2. auto MyMessageBox = dll.GetProcAddress_stdcall<int, HWND, LPCSTR, LPCSTR, UINT>("MessageBoxA");
  3. MyMessageBox(NULL, "第一次使用MessageBox!", "11111", MB_OK);
  4. MyMessageBox(NULL, "第二次使用MessageBox!", "22222", MB_ICONASTERISK);

使用GetProcAddress_XXXX函式時請注意:由於本類的解構函式中會自動執行FreeLibrary函式釋放DLL庫,在一個類物件被析構後,使用它獲取的函式地址將可能失效(大概率會失效)。所以,請保證一個物件獲取的函式地址不會在它被析構後呼叫。下面是一個錯誤示範:

  1. auto Fun1()
  2. { CSzxRunDll2 dll(TEXT("XXX.dll")); return dll.GetProcAddress_cdecl<int, int, int>("Add"); }
  3. void main()
  4. {
  5. auto fun = Fun1();
  6. int n = fun(1, 2); // 可能崩潰
  7. std::cout << n;
  8. }

此例中,Fun1函式中雖獲取了Add函式的地址,但在Fun1函式返回之前,物件“dll”已執行了解構函式,它構造時載入的“XXX.dll”已被解構函式中執行的FreeLibrary釋放,導致原有的函式地址失效。之所以說“可能崩潰”,是因為如果在一個類物件載入DLL前,就已載入過該DLL,則使用一次FreeLibrary不會將其釋放,只會減少引用次數,所以原函式地址依然有效。如果要在多個函式內呼叫一個函式地址,請將CSzxRunDll2物件定義為全域性變數或靜態變數。

BuildCppDecoratedName函式BuildCppDecoratedName模板為靜態成員函式模板, 作用是根據原函式的資訊生成C++方式修飾過的函式名稱. 有兩種呼叫方法:

  1. 通過物件呼叫: 類物件 . BuildCppDecoratedName<...>(...);
  2. 直接呼叫: CSzxRunDll2::BuildCppDecoratedName<...>(...);

第一個模板引數為dll函式返回值型別, 後面的是dll函式引數型別列表. 例項化時, 第一個函式引數是dll函式名稱, 第二個引數是呼叫約定, 可以為以下三個值中任意一個: CSzxRunDll2::Stdcall / CSzxRunDll2::Cdecl / CSzxRunDll2::Fastcall, 可不填, 預設為cdecl. 以下是一個使用該函式呼叫C++方式匯出函式的例子:

  1. CSzxRunDll2 dll(TEXT("math.dll"));
  2. // 要呼叫的函式原型: long __cdecl Add(int a, int b);
  3. std::string str = dll.BuildCppDecoratedName<long, int, int>("Add", CSzxRunDll2::Cdecl);
  4. std::cout << "Result: " << dll.CallDllFunc2_cdecl<long>(str.c_str(), 111, 22);

已知問題:

  1. BuildCppDecoratedName不支援DLL函式引數中有結構體(struct)、列舉(enum)、引用(reference).
  2. 在使用CallDllFunc2_XXX函式呼叫引數列表中帶有引用(reference)的DLL函式時, 若按上述的常規方式會出現錯誤, 必須使用結構體傳參, 如下所示:
  1. // 要呼叫的函式原型: void Add(const int &a, const int &b, long &result);
  2. int re;
  3. struct MyStruct
  4. {
  5. const int& a;
  6. const int& b;
  7. int& result;
  8. } param = { 222, 3000, re };
  9. const char* name = "?Add@@YAXABH0AAJ@Z"; // 修飾過的函式名, 帶引用的目前本類不支援生成,可使用dumpbin工具生成
  10. dll.CallDllFunc2_cdecl(name, param);

此類還在繼續開發完善中. 若有任何問題, 歡迎指正.

版權說明

本文由szx0427撰寫,由Icys獲得授權後在CnBlogs代為其釋出釋出。

知乎通道