前文
ご存じのとおり、動的リンクライブラリ(DLL)の使用には、接続する2つの方法のいずれかが含まれます。
ロード時リンクと
実行時リンクです。 後者の場合、オペレーティングシステムが提供するAPIを使用して、目的のモジュール(ライブラリ)をロードし、その中の必要なプロシージャのアドレスを検索する必要があります。 ラッパーは多数ありますが、残念ながら、私が遭遇したものはすべて非常に複雑で、不要なコードでオーバーロードされています。 提案されたソリューションは、もともと実行可能モジュール(EXE)からDLLに保存された関数を呼び出すことを目的としており、比較的簡単な実装と(より重要なことですが)クライアントコードでの使いやすさによって区別されます。
純粋なWin32 APIを使用したソリューションは、次のようになります(実際には、MSDNからのフラグメントの繰り返しです)。
typedef int (__cdecl *some_proc_t)(LPWSTR); HINSTANCE hlib = LoadLibrary(_T("some.dll")); myproc_t proc_addr = NULL; int result = -1; if (hlib) { proc_addr = (some_proc_t) GetProcAddress(hlib, "SomeProcName"); if (proc_addr) { result = proc_addr(L"send some string to DLL function"); printf("Successfully called DLL procedure with result %d", result); } FreeLibrary("some.dll"); }
この記事では、これらのシステムコールの使いやすいラッパーを提供します。 使用例:
ntprocedure<int(LPWSTR)> some_proc_("SomeProcName", _T("some.dll")); try { int result = some_proc_(L"send some string to DLL function"); printf("Successfully called DLL procedure with result %d", result); } catch (...) { printf("Failed to call DLL procedure"); }
リストからわかるように、必要なことは、呼び出された関数の型に対応するテンプレートパラメータを使用して
ntprocedureオブジェクトを作成し、その名前とライブラリ名をコンストラクタに渡すことだけです。
実装
ラッパーの実装の説明を開始する前に、多くの人にとっては役に立たないように見える簡単な宣言を含む小さなヘッダーファイルを提供します。
Common.hファイル #pragma once #include #include <string> #define NS_BEGIN_(A) namespace A { #define NS_BEGIN_A_ namespace { #define NS_END_ } #define NO_EXCEPT_ throw() #define THROW_(E) throw(E) #define PROHIBITED_ = delete //============================================================================= typedef std::basic_string< TCHAR, std::char_traits<TCHAR>, std::allocator<TCHAR> > tstring;
開発中のテンプレートクラスが呼び出し時点で関数のように動作し、任意の数と種類の引数をサポートできるようにする方法について考えます。 最初に頭に浮かぶのは、汎用ファンクターを使用することです。 このようなラッパーの既知の実装の作成者は、まさにそれを行います。 この場合、引数の数に応じてファンクターテンプレートの部分的な特殊化が使用されるか、関数呼び出し演算子の多重オーバーロードが使用されます。 原則として、問題はマクロの助けなしでは完全ではありません。 幸いなことに、引数の数が可変のテンプレートがC ++ 11に登場しました。
R operator () (Args ... args)
実際、このケースでは、はるかに簡単に実行できます。つまり、関数呼び出し演算子の代わりにキャスト演算子を使用します。
Tが関数ポインターのタイプであり、アドレスがそのアドレスが格納される変数である場合、次のステートメントを定義できます。
operator T() { return reinterpret_cast<T>(address); }
以下は、ntprocedure.hヘッダーファイルの完全なコードです。
Ntprocedure.hファイル #pragma once #include "common.h" #include <memory> #include <string> #include <type_traits> NS_BEGIN_(ntutils) NS_BEGIN_(detail) class ntmodule; class ntprocedure_base { ntprocedure_base(const ntprocedure_base&) PROHIBITED_; void operator=(const ntprocedure_base&) PROHIBITED_; public: ntprocedure_base(const std::string& a_proc_name, const tstring& a_lib_name);
注意深い読者が気付いたいくつかの点-プロシージャのアドレスは
m_function変数に格納され、1回計算され、2番目の瞬間
-ntmoduleクラスのオブジェクトへの共有ポインターが基本クラスに格納されます。 ロードされたモジュールに関する情報を保存していると推測するのは簡単です。
shared_ptrを使用すると、モジュールを使用するすべてのプロシージャオブジェクトを破棄した後、モジュールを自動的にアンロードできます。
Ntmodule.hファイル #pragma once #include "common.h" #include "resource_ptr.h" #include <list> #include <memory> NS_BEGIN_(ntutils) NS_BEGIN_(detail) class ntmodule : public std::enable_shared_from_this<ntmodule> { ntmodule(const ntmodule&) PROHIBITED_; void operator=(const ntmodule&) PROHIBITED_; public: typedef std::list<ntmodule*> container_t; ntmodule(const tstring& a_name);
ntmoduleクラスの定義を考慮してください。
Ntmodule.cppファイル #include "stdafx.h" #include "ntmodule.h" #include "ntprocedure.h" #include <cassert> #include <exception> ntutils::detail::ntmodule::ntmodule(const tstring& a_name) : m_name(a_name) { assert(!a_name.empty()); cached().push_back(this); } ntutils::detail::ntmodule::~ntmodule() { cached().remove(this); } const tstring& ntutils::detail::ntmodule::name() const { return m_name; } FARPROC WINAPI ntutils::detail::ntmodule::address( const std::string& a_name ) { assert(!a_name.empty()); if (!m_handle) { m_handle.reset(::LoadLibrary(m_name.c_str())); } if (!m_handle) { std::string err("LoadLibrary failed"); throw std::runtime_error(err); } return m_handle ? ::GetProcAddress(m_handle, a_name.c_str()) : 0; } std::shared_ptr<ntutils::detail::ntmodule> ntutils::detail::ntmodule::share() { return shared_from_this(); } ntutils::detail::ntmodule::container_t& ntutils::detail::ntmodule::cached() { static container_t* modules = new container_t; return *modules; }
ご覧のとおり、使用されているすべてのモジュールへのポインターは静的リストに格納されています。 これにより、キャッシュが提供されます。
ntmoduleクラスのコンストラクターはリスト内のオブジェクトへのポインターを配置し、デストラクタはそれを削除します。 ntprocedureクラスの定義により、全体像が明確になります。
Ntprocedure.cppファイル #include "stdafx.h" #include "ntmodule.h" #include "ntprocedure.h" #include <cassert> #include <exception> ntutils::detail::ntprocedure_base::ntprocedure_base( const std::string& a_proc_name, const tstring& a_lib_name ) : m_name(a_proc_name), m_module(nullptr) { assert(!a_proc_name.empty()); assert(!a_lib_name.empty()); for (auto module : ntmodule::cached()) { // Perform case insensitive comparison: if (!lstrcmpi(module->name().c_str(), a_lib_name.c_str())) { m_module = module->share(); break; } } if (!m_module) { m_module = std::make_shared<ntmodule>(a_lib_name); } } ntutils::detail::ntprocedure_base::~ntprocedure_base() { } FARPROC WINAPI ntutils::detail::ntprocedure_base::address() { FARPROC addr = m_module->address(m_name); if (!addr) { std::string err("GetProcAddress failed"); throw std::runtime_error(err); } return addr; } const std::string& ntutils::detail::ntprocedure_base::name() const { return m_name; }
ntprocedure_baseコンストラクター
は 、静的リスト内の必要なモジュールを名前で
検索します。 そのようなモジュールが見つかった場合、呼び出しmodule->
share()はリスト内のポインターに基づいて共有ポインターを作成します;そのようなモジュールがまだない場合は、新しいオブジェクトが作成されます。
初めて使用するモジュールごとに、
GetModuleHandle()関数に依存せずに
LoadLibrary()を呼び出してから、
shared_ptrを使用して作成されたオブジェクトを制御することに注意してください。 これにより、
LoadLibrary()および
FreeLibrary()への直接呼び出しを使用するコードを使用して、同じプロジェクトで作成されたラッパーを一緒に安全に使用できます。
以上です。 そうそう、
resouce_ptr型がコードに表示されます。 これは、HANDLE、HMODULEなどの型のRAIIラッパーにすぎません。 interenoである人のために、私は実装をもたらします:
Resource_ptr.hファイル #pragma once #include "common.h" #include "windows.h" #include <cassert> #include <memory> NS_BEGIN_(ntutils) template<typename HTag_> struct resource_close { void operator()(typename HTag_::handle_t) const NO_EXCEPT_; }; struct handle_tag { typedef HANDLE resource_t; }; struct hmodule_tag { typedef HMODULE resource_t; }; template<> struct resource_close<handle_tag> { void operator()(handle_tag::resource_t a_handle) const NO_EXCEPT_ { bool status = !!::CloseHandle(a_handle); assert(status); } }; template<> struct resource_close<hmodule_tag> { void operator()(hmodule_tag::resource_t a_handle) const NO_EXCEPT_ { bool status = !!::FreeLibrary(a_handle); assert(status); } }; template< typename RTag_, typename RTag_::resource_t RInvalid_, typename RFree_ = resource_close<RTag_> > class resource_ptr { typedef typename RTag_::resource_t resource_t; typedef RFree_ deletor_t; resource_ptr(const resource_ptr&) PROHIBITED_; void operator=(const resource_ptr&) PROHIBITED_; public: resource_ptr() NO_EXCEPT_ : m_resource(RInvalid_) { } resource_ptr(resource_t a_resource) NO_EXCEPT_ : m_resource(a_resource) { }
確かにそれだけです。 ご清聴ありがとうございました。ご意見をお聞かせください。