Простая В Использовании Оболочка Вокруг Loadlibrary() И Getprocaddress().



Преамбула Известно, что использование динамически подключаемых библиотек (DLL) предполагает один из двух методов связывания: связывание во время загрузки ( связывание во время загрузки ) и привязка времени выполнения ( связывание во время выполнения ).

В последнем случае вам необходимо использовать API, предоставляемый операционной системой, для загрузки необходимого модуля (библиотеки) и поиска в нем адреса необходимой процедуры.

Оберток много, но, к сожалению, все встречавшиеся мне очень сложны и перегружены ненужным кодом.

Предлагаемое решение изначально предназначено для вызова функций, хранящихся в DLL, из исполняемых модулей (EXE), и отличается относительной простотой реализации и (что гораздо важнее) удобством использования в клиентском коде.

Решение с использованием чистого 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"); }

Как видно из листинга, все, что вам нужно сделать, это создать объект нтпроцедура с параметрами шаблона, соответствующими типу вызываемой функции, передавая в конструктор ее имя и имя библиотеки.



Выполнение

Прежде чем мы начнем описывать реализацию оболочки, я предоставлю небольшой заголовочный файл с тривиальными объявлениями, которые многие сочтут бесполезными и от которых легко избавиться, но которые используются в моем коде.

Файл common.h

#pragma once #include "tchar.h" #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)

На самом деле в нашем случае можно поступить гораздо проще, а именно использовать оператор приведения вместо оператора вызова функции.

Если Т является типом указателя функции, и адрес является переменной, в которой хранится ее адрес, вы можете определить следующий оператор:

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); // Constructor. virtual ~ntprocedure_base() = 0; // Destructor. FARPROC WINAPI address(); // Get the procedure address. const std::string& name() const; // Get the procedure name. private: std::string m_name; std::shared_ptr<ntmodule> m_module; }; NS_END_ template<typename T> class ntprocedure : public detail::ntprocedure_base { public: typedef typename std::remove_pointer<T>::type callable_t; typedef callable_t *callable_ptr_t; ntprocedure(const std::string& a_proc_name, const tstring& a_lib_name) : ntprocedure_base(a_proc_name, a_lib_name), m_function(nullptr) { } // Constructor. virtual ~ntprocedure() { } // Destructor. operator callable_ptr_t() { if (!m_function) { m_function = reinterpret_cast<callable_ptr_t>(address()); } return m_function; } // Return stored function to invoke. private: callable_ptr_t m_function; }; NS_END_

Пара моментов, которые заметил внимательный читатель — адрес процедуры хранится в переменной m_function и вычисляется один раз, а второй момент заключается в том, что базовый класс хранит общий указатель на объект класса нтмодуль .

Нетрудно догадаться, что он хранит информацию о загруженном модуле.

Применение общий_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); // Constructor. ~ntmodule(); // Destructor. const tstring& name() const; // Get the module name. FARPROC WINAPI address(const std::string& a_name); // Get the procedure address. std::shared_ptr<ntmodule> share(); // Share this object. static container_t& cached(); // Return the reference to the cache. private: tstring m_name; hmodule_ptr m_handle; }; NS_END_ NS_END_

Рассмотрим определение класса 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; }

Как видите, указатели на все используемые модули хранятся в статическом списке.

Это обеспечивает кэширование.

Конструктор класса нтмодуль помещает указатель на свой объект в список, а деструктор удаляет его.

Определение класса 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 нужный модуль ищется в статическом списке по его названию.

Если такой модуль найден, то вызываем модуль-> делиться() создает общий указатель на основе существующего указателя в списке; если такого модуля еще нет, создается новый объект. Обратите внимание, что для каждого модуля, который мы используем впервые, мы вызываем ЗагрузитьБиблиотеку() не полагаясь на функцию ПолучитьМодулеХандле() и только потом управлять созданными объектами с помощью общий_ptr .

Это позволяет безопасно использовать сгенерированную оболочку вместе в одном проекте с кодом, использующим прямые вызовы.

ЗагрузитьБиблиотеку() И БесплатнаяБиблиотека() .

Вот и все.

Ах да, тип появляется в коде ресурс_ptr .

Это не что иное, как RAII-обертка над такими типами, как HANDLE, HMODULE и т. д. Кому интересно вот реализация: Файл resources_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) { } // Constructor. explicit operator bool() const NO_EXCEPT_ { return m_resource && m_resource != RInvalid_; } // Operator bool().

operator const resource_t&() const NO_EXCEPT_ { return m_resource; } // Get the stored handle value. void reset(resource_t a_resource = resource_t()) NO_EXCEPT_ { resource_t old = m_resource; m_resource = a_resource; if (old != resource_t() && old != RInvalid_) { m_deletor(old); } } ~resource_ptr() NO_EXCEPT_ { if (m_resource != resource_t() && m_resource != RInvalid_) { m_deletor(m_resource); } } // Destructor. private: resource_t m_resource; deletor_t m_deletor; }; typedef resource_ptr<handle_tag, INVALID_HANDLE_VALUE> handle_ptr; typedef resource_ptr<hmodule_tag, NULL> hmodule_ptr; NS_END_

Это все точно.

Спасибо за внимание, буду рад вашим комментариям! Теги: #C++ #c++11 #win32 api #dll #C++ #Разработка для Windows

Вместе с данным постом часто просматривают: