本次介紹一種調用dll函數的通用簡潔的方法,消除了原來調用方式的重復與繁瑣,使得我們調用dll函數的方式更加方便簡潔。用過dll的人會發現c++中調用dll中的函數有點繁瑣,調用過程是這樣的:在加載dll后還要定義一個對應的函數指針類型,再調用GetProcAddress獲取函數地址,再轉成函數指針,最后調用該函數。下面是調用dll中Max和Get函數的例子。
void TestDll() { typedef int(*pMax)(int a,int b); typedef int(*pGet)(int a); HINSTANCE hMode =LoadLibrary("MyDll.dll");if(hMode==nullptr) return; PMax Max = (PMax)GetProcAddress(hDLL,"Max"); if(Max==nullptr) return; int ret =Max(5,8); //8 PMin Get = (PMin)GetProcAddress(hDLL,"Get"); if(Get==nullptr) return; int ret =Get(5); //5 FreeLibrary(hDLL); }
這段代碼看起來很繁瑣,因為我沒每用一個函數就需要先定義一個函數指針,然后再根據名稱獲取函數地址,最后調用。如果一個dll中有上百個函數的話,這種重復而繁瑣的定義會讓人吐的。其實獲取函數地址和調用函數的過程是重復邏輯,應該消除,我不想每次都定義一個函數指針和調用GetProcAddress,我覺得可以用一種簡潔通用的方式去調用dll中的函數。我希望調用dll中的函數就像調用普通的函數一樣,即傳入一個函數名稱和函數的參數就可以實現函數的調用了。就類似於:
Ret CallDllFunc(const string&funName, T arg)
如果以這種方式調用的話,我就能避免繁瑣的函數指針定義以及反復的調用GetProcAddress了。
一種可行的解決方案
如果要按照
Ret CallDllFunc(const string& funName, T arg)
這種方式調用的話,首先我要把函數指針轉換成一種函數對象或者泛型函數,這里我們可以用std::function去做這個事情,即通過一個函數封裝GetProcAddress,這樣通過函數名稱我就能獲取一個泛型函數std::function,我希望這個function是通用的,不論dll中是什么函數都可以轉換成這個function, 最后調用這個通用
的function就可以了。但是調用這個通用的function還有兩個問題需要解決:
- 不同函數的不同類型返回值怎么處理,因為函數的返回值可能是某些類型,如何以一種通用的返回值來消除這種不同返回值導致的差異呢?
- 函數的入參數目可能任意個數,且類型也不盡相同,如何來消除入參個數和類型的差異呢?
我們一個個解決問題吧,首先看看如何封裝GetProcAddress,將函數指針轉換成std::function。通過如下代碼就可以了。
template <typename T> std::function<T> GetFunction(const string&funcName) { FARPROC funAddress = GetProcAddress(m_hMod, funcName.c_str()); return std::function<T>((T*)(funAddress)); }
其中T是std::function的模板參數,即函數類型的簽名。如果我們要獲取上面例子中,Max和Get函數,則可以這樣獲取:
auto fmax = GetFunction<int(int, int)>("Max"); auto fget = GetFunction<int(int)>("Get");
這種方式比之之前先定義函數指針再調用GetProcAddress的方式更簡潔通用。
再看看如何解決函數返回值和入參不統一的問題,關於這個問題,其實在前面的博文中就講到了,不知道的童鞋看這里:
是的,還是通過result_of和可變參數模板來搞定。最終的調用函數是這樣的:
template <typename T, typename... Args> typename std::result_of<std::function<T>(Args...)>::type ExcecuteFunc(const string& funcName,Args&&... args) { return GetFunction<T>(funcName)(args...); }
上面的例子中要調用Max和Get函數,這樣就行了:
auto max = ExcecuteFunc<int(int, int)>("Max", 5, 8); auto ret = ExcecuteFunc<int(int)>("Get", 5);
怎么樣,比之之前的調用方式是不是簡潔直觀多了,沒有了繁瑣的函數指針的定義,沒有了反復的調用GetProcAddress及其轉換和調用。
如果要限定調用方式就在參數前面加,如
ExcecuteFunc<int __stdcall(int, int)>
最后看看完整的代碼吧。
#include <Windows.h> #include <string> #include <map> #include <functional> using namespace std; class DllParser { public: DllParser() { } ~DllParser() { UnLoad(); } bool Load(const string& dllPath) { m_hMod = LoadLibrary(dllPath.data()); if (nullptr == m_hMod) { printf("LoadLibrary failed\n"); return false; } return true; } bool UnLoad() { if (m_hMod == nullptr) return true; auto b = FreeLibrary(m_hMod); if (!b) return false; m_hMod = nullptr; return true; } template <typename T> T* GetFunction(const string& funcName) { auto addr = GetProcAddress(m_hMod, funcName.c_str()); return (T*) (addr); } template <typename T, typename... Args> typename std::result_of<std::function<T>(Args...)>::type ExcecuteFunc(const string& funcName, Args&&... args) { auto f = GetFunction<T>(funcName); if (f == nullptr) { string s = "can not find this function " + funcName; throw std::exception(s.c_str()); } return f(std::forward<Args>(args)...); } private: HMODULE m_hMod; std::map<string, FARPROC> m_map; };
c++11 boost技術交流群:296561497,歡迎大家來交流技術。