Преамбула Известно, что использование динамически подключаемых библиотек (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
-
Конвертер Из Hdmi/Dp++ В Mipi Dsi
19 Oct, 24