В этом разделе описывается экспериментальная особенность, позволяющая импортировать искалеченные символы из дл. Хотя эта функция уникальна для этой библиотеки и выглядит довольно многообещающе, она не подвергается жесткой проверке и, следовательно, не считается стабильной.
В качестве краткого примера мы можем легко импортировать следующие функции:
namespace foo {
int bar(int);
double bar(double);
}
И импорт выглядит так:
auto f1 = import_mangled<int(int)>("library.dll", "foo::bar");
auto f2 = import_mangled<double(double)>("library.dll", "foo::bar");
cout << f1(42) << endl;
cout << f2(3.2) << endl;
В настоящее время реализованы Itanium ABI и MSVC ABI. MSVC ABI требует поддержки boost.spirit.x3, что позволяет использовать только MSVC 2015. API Itanium требует C++11.
- Гцк
- Клэнг
- MSVC 2015
- Intel C++
API Itanium не импортирует функции возврата или глобальные переменные.
Ядром искалеченного импорта является классsmart_library
. Он может импортировать функции и переменные в их искаженной форме; для этого смарт-библиотека считывает весь контур библиотеки и распутывает каждую точку входа в нее. Это также означает, что этот класс должен быть построен только один раз.
Для импорта всех методов в следующей библиотеке мы будем использоватьsmart_library
.
Первое, что нужно сделать при создании собственных плагинов, это определить интерфейс плагина. Существует пример абстрактного класса, который будет нашим API плагина:
#include <string>
namespace space {
class BOOST_SYMBOL_EXPORT my_plugin
{
std::string _name;
public:
std::string name() const;
float calculate(float x, float y);
int calculate(int, x, int y);
static std::size_t size();
my_plugin(const std::string & name);
my_plugin();
~my_plugin_api();
static int value;
};
}
Итак, теперь у нас есть определение для плагина, поэтому мы используем его в следующем полнофункциональном примере. Имейте в виду, что существует более удобное решение для импортных функций-членов, которое будет рассмотрено позже. Этот пример показывает, однако, чтоsmart_lib
предоставляет в качестве функций.
Сначала мы создали умную библиотеку. Имейте в виду, что класс псевдонимов необходим для обеспечения алии типа для my_plugin.
#include <boost/dll/smart_library.hpp>
#include <iostream>
#include <memory>
namespace dll = boost::dll;
struct alias;
int main(int argc, char* argv[]) {
boost::filesystem::path lib_path(argv[1]);
dll::smart_lib lib(lib_path);
Для того чтобы создать класс, нужно будет выделить память. Это, конечно, означает, что нам нужно знать размер; к сожалению, он не экспортируется в dll, поэтому мы добавили функцию статического размера для экспорта. Статические используются как простые функции.
Поэтому мы импортируем его, называем и выделяем память.
auto size_f = lib.get_function<std::size_t()>("space::my_plugin::size");
auto size = size_f();
std::unique_ptr<char[], size> buffer(new char[size]);
alias & inst = *reinterpret_cast<alias*>(buffer.get());
Теперь у нас есть размер памяти и ссылка на наш тип псевдонима. Для того чтобы его использовать, нужно зарегистрировать тип как псевдоним. Это позволит смарт-библиотеке определить имя типа.
lib.add_type_alias("space::my_plugin");
Для того чтобы использовать класс, нам, конечно, нужно его инициализировать, т.е. назвать конструктором. Itanium ABI может также использовать распределительный конструктор. Вот почему конструктор может иметь две функции; поскольку мы уже выделили память, мы используем стандартную версию конструктора из строки. Поэтому мы выбираем конструктора, передавая подпись.
auto ctor = lib.get_constructor<alias(const std::string&)>();
ctor.call_standard(&inst, "MyName");
Так как класс теперь инициализируется, мы можем назвать метод имени. Если функция является константой и/или летучим параметром типа, принятый в качестве типа, должен иметь те же квалификаторы.
auto name_f = lib.get_mem_fn<const alias, std::string()>("name");//import the name function
std::cout << "Name Call: " << (inst.*name_f)() << std::endl;
Перегруженные функции могут быть импортированы только отдельно.
auto calc_f = lib.get_mem_fn<alias, float(float, float)>("calculate");
auto calc_i = lib.get_mem_fn<alias, int(int, int)> ("calculate");
std::cout << "calc(float): " << (inst.*calc_f)(5., 2.) << std::endl;
std::cout << "calc(int) : " << (inst.*calc_f)(5, 2) << std::endl;
Импорт статической переменной выполняется как с простой переменной.
auto & var = lib.get_variable<int>("space::my_plugin::value");
cout << "value " << var << endl;
С тех пор, как мы закончили, мы называем дескруктора класса.
auto dtor = lib.get_destructor<alias>();
dtor.call_standard(&inst);
std::cout << "plugin->calculate(1.5, 1.5) call: " << plugin->calculate(1.5, 1.5) << std::endl;
}
Назад к вершине
Теперь показано, как искалеченные и методы могут быть импортированы. Это, однако, довольно универсальный способ, поэтому обеспечивается более простой интерфейс, который также позволяет получить доступ к type_info объекта.
Мы возьмем тот же класс и импортируем те же методы, но сделаем это с помощью импортных функций.
Мы поместили библиотеку в общий указатель, потому что каждый импорт будет содержать такой указатель. То есть мы не хотим его копировать.
#include <boost/dll/smart_library.hpp>
#include <boost/dll/import_mangled.hpp>
#include <boost/dll/import_class.hpp>
int main(int argc, char* argv[]) {
boost::filesystem::path lib_path(argv[1]);
smart_library lib(lib_path);// smart library instance
Как и в предыдущем примере, нам нужен размер класса.
auto size_f = import_mangled<std::size_t()>("space::my_plugin::size");
auto size = (*size_f)();
С другой стороны, мы также можем легко импортировать переменные с этой функцией.
auto value = import_mangled<int>(lib, "space::my_plugin::value");
Мы делаем форвардную декларацию по первому вызову и напрямую обращаемся к конструктору. Это довольно просто и позволяет вызвать конструктора напрямую. Деструктор будет вызываться автоматически.
auto cl = import_class<class alias, const std::string&>(lib, "space::my_plugin::some_class", size, "MyName");
Для запуска функции сначала потребуется импортировать ее.
auto name = import_mangled<const alias, std::string()>(lib, "name");
std::cout << "Name: " << (cl->*name)() << std::endl;
Для перегруженных функций мы можем импортировать их как группы, что даст нам объект, содержащий перегрузки.
auto calc = import_mangled<alias, float(float, float), int(int, int)>(lib, "calculate");
std::cout << "Calc(float): " (cl->*calc)(5.f, 2.f) << std::endl;
std::cout << "Calc(int): " (cl->*calc)(5, 2) << std::endl;
Кроме того, мы можем получить доступ к такой информации.
std::type_info &ti = cl.get_type_info();
Назад к вершине
В примере не рассматривался вопрос о том, как он обрабатывается, если квалификация отличается для перегруженной функции. Это можно сделать, пройдя класс снова с другой квалификацией - подпись функции всегда выберет последнюю предоставленную.
Если это есть в нашем плагине:
struct plugin
{
void f(int);
void f(double);
void f(int) const;
void f() const;
void f() volatile;
void f(int) volatile;
void f(double); const volatile;
};
Мы можем импортировать их все сразу, со следующей командой:
auto f = import_class<
alias, f(int), f(double),
const alias, f(int), f(),
volatile alias, f(), f(int),
const volatile alias, f(double)//const volatile
>(lib, "f");