C++11はdllをロードし、その関数を呼び出すdllヘルプクラスを実現する

4398 ワード

C++でdllの関数を呼び出すのは煩雑で、dllをロードした後、対応する関数ポインタのタイプを定義し、GetProcAddressを呼び出して関数アドレスを取得し、関数ポインタに変換し、最後に関数を呼び出す必要があります.次のようになります.
void TestDll()
{
	typedef int(*pMax)(int a, int b);
	typedef int(*pGet)(int a);

	HINSTANCE hDll = LoadLibraryA("mydll.dll");
	if (hDll == nullptr)
		return;

	pMax Max = (pMax)GetProcAddress(hDll, "Max");
	if (Max == nullptr)
		return;

	int ret = Max(5, 8);

	pGet Get = (pGet)GetProcAddress(hDll, "Get");
	if (Get == nullptr)
		return;

	int ret = Get(5);

	FreeLibrary(hDll);
}

このコードは、1つの関数を使用するたびに関数ポインタを定義し、名前に基づいて関数アドレスを取得し、最後に呼び出す必要があるため、煩雑に見えます.1つのdllに100以上の関数がある場合、このような煩雑な定義は煩わしいです.実は関数アドレスと呼び出し関数を取得する過程は繰り返し論理であり、削除すべきであり、毎回関数ポインタを定義し、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のどんな関数でもこのstd::functionに変換し、最後にこの汎用的なfunctionを呼び出せばいい.しかし、この汎用的なfunctionを呼び出すには2つの問題があります.
  • 関数の戻り値は異なるタイプである可能性がありますが、このような異なる戻り値による違いを一般的な戻り値で除去するとしたら?
  • 関数の入力パラメータ目は任意の数であり、タイプも異なる可能性があります.どのようにして入力パラメータとタイプの違いを解消しますか?

  • まずGetProcAddressをカプセル化する方法を見て、関数ポインタをstd::functionに変換します.コードは以下の通りです.
    template 
    std::function GetFunction(const string& funcName)
    {
    	FARPROC funAddress = GetProcAddress(m_hMod, funcName.c_str());
    	return std::function((T*)(funAddress));
    }

    ここでTはstd::functionのテンプレートパラメータ,すなわち関数タイプの署名である.上記の例のMaxとGet関数を取得するには、次のようにします.
    auto fmax = GetFunction("Max");
    auto fget = GetFunction("Get");

    この方法は、GetProcAddressを呼び出す前に関数ポインタを定義する方法よりも簡潔で汎用的です.
    関数の戻り値とパラメータが統一されていない問題を解決する方法を見てみましょう.result_ofと可変パラメータテンプレートで解決し、最終的な呼び出し関数は以下の通りです.
    template 
    typename std::result_of<:function>(Args...)>::type ExcecuteFunc(const string& funcName, Args&&... args)
    {
    	return GetFunction(funcName)(arg...);
    }
    

    上記の例では、Max関数とGet関数を呼び出します.これでいいです.
    auto max = ExecuteFunc("Max", 5, 8);
    auto ret = ExecuteFunc("Ret", 5);

    以前の呼び出し方式よりも簡潔で直感的で、煩雑な関数ポインタの定義がなく、GetProcAddressとその変換と呼び出しを繰り返していない.
    完全なコードは次のとおりです.
    #pragma once
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    
    class DllParser
    {
    public:
    
    	DllParser():m_hMod(nullptr)
    	{
    	}
    
    	~DllParser()
    	{
    		UnLoad();
    	}
    
    	bool Load(const string& dllPath)
    	{
    		m_hMod = LoadLibraryA(dllPath.data());
    		if (nullptr == m_hMod)
    		{
    			printf("LoadLibrary failed
    "); 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 std::function GetFunction(const string& funcName) { auto it = m_map.find(funcName); if (it == m_map.end()) { auto addr = GetProcAddress(m_hMod, funcName.c_str()); if (!addr) return nullptr; m_map.insert(std::make_pair(funcName, addr)); it = m_map.find(funcName); } return std::function((T*) (it->second)); } template typename std::result_of<:function>(Args...)>::type ExcecuteFunc(const string& funcName, Args&&... args) { auto f = GetFunction(funcName); if (f == nullptr) { string s = "can not find this function " + funcName; throw std::exception(s.c_str()); } return f(std::forward(args)...); } private: HMODULE m_hMod; std::map m_map; };

    実装の鍵は、FARPROCを関数ポインタとしてstd::functionにコピーし、可変パラメータ実行を呼び出す方法です.関数の戻り値はstd::result_を通ります.ofは、異なる戻り値のdll関数が同じ方法で呼び出されるように汎化される.
     
    参考資料:
    『深くC++11を応用する:コード最適化と工程級応用』