跳到主要内容

Boost.DLL

动机

在运行时将特定功能添加到现有软件应用程序可能会很有用。 此类扩展或插件通常使用在运行时加载的动态库模块(DLL,SO/DSO)来实现。

该库旨在以可移植的跨平台方式简化使用C ++的插件开发。

库提供了一种跨平台的可移植方式:

  • 加载库
  • 导入任何本地函数和变量
  • 为C ++ mangled函数和符号创建别名
  • 查询库中的节和导出的符号
  • 自加载和自查询
  • 通过导出的符号获取程序和模块位置

入门

Boost.DLLheader-only库。 要开始使用该库,您只需要包含<boost/dll.hpp>头文件。 之后,您可以自由导入和导出函数和变量。 导入代码需要链接到boost_filesystemboost_system库。

如果要加载库,只需构造boost::dll::shared_library类,并将库的路径作为参数:

boost::dll::shared_library lib("/test/boost/application/libtest_library.so");

现在,您可以使用getget_alias成员函数轻松地从该库中导入符号:

int plugin_constant = lib.get<const int>("integer_variable");
boost::function<int()> f = lib.get<int()>("function_returning_int");
int& i = lib.get_alias<int>("alias_to_int_variable");

对于boost::dll::shared_library,只有在boost::dll::shared_library实例未销毁之前,才可以使用导入的符号。

使用boost::dll::library_info查询库,并使用boost::dll::symbol_locationboost::dll::this_line_locationboost::dll::program_location获取符号信息。

要导入单个函数或变量,可以使用以下一个衬里:

using namespace boost;

// `extern "C"` - specifies C linkage: forces the compiler to export function/variable by a pretty (unmangled) C name.
#define API extern "C" BOOST_SYMBOL_EXPORT

// Importing function.
auto cpp11_func = dll::import<int(std::string&&)>(
path_to_shared_library, "i_am_a_cpp11_function"
);

// Export (DLL/DSL sources):
namespace some_namespace {
API int i_am_a_cpp11_function(std::string&& param) noexcept;
// ^-------------------- function name to use in dll::import<>
}

//Functions description:
import<T>(...)
// Importing  variable.
shared_ptr<std::string> cpp_var = dll::import<std::string>(
path_to_shared_library, "cpp_variable_name"
);

// Export (DLL/DSL sources):
namespace your_project_namespace {
API std::string cpp_variable_name;
}

// Functions description:
import<T>(...)
// Importing function by alias name
auto cpp_func = dll::import_alias<std::string(const std::string&)>(
path_to_shared_library, "pretty_name"
);

// Export (DLL/DSL sources):
namespace some_namespace {
std::string i_am_function_with_ugly_name(const std::string& param) noexcept;
}

// When you have no control over function sources or wish to specify another name.
BOOST_DLL_ALIAS(some_namespace::i_am_function_with_ugly_name, pretty_name)

// Functions description:
import_alias<T>(...)
BOOST_DLL_ALIAS

使用导入的变量或函数是安全的,因为从import<T>(...)import_alias<T>(...)函数返回的变量在内部保存对共享库的引用。

BOOST_SYMBOL_EXPORT只是Boost.Config中的一个宏,它扩展为__declspec(dllexport)__attribute__((visibility("default")))。 您可以自由使用自己的宏进行导出。

Linux/POSIX/MacOS上,使用"dl"进行链接。 还建议使用“-fvisibility = hidden” flag。

教程

提供本教程的目的是使您了解如何创建和使用插件。

插件基础

创建自己的插件时,要做的第一件事是定义插件接口。 有一个抽象类的示例,它将作为我们的插件API:

#include <boost/config.hpp>
#include <string>

class BOOST_SYMBOL_VISIBLE my_plugin_api {
public:
virtual std::string name() const = 0;
virtual float calculate(float x, float y) = 0;

virtual ~my_plugin_api() {}
};

现在,让我们制作一个DLL/DSO库,该库将保存插件接口的实现,并使用extern“ C”BOOST_SYMBOL_EXPORT导出它:

#include <boost/config.hpp> // for BOOST_SYMBOL_EXPORT
#include "../tutorial_common/my_plugin_api.hpp"

namespace my_namespace {

class my_plugin_sum : public my_plugin_api {
public:
my_plugin_sum() {
std::cout << "Constructing my_plugin_sum" << std::endl;
}

std::string name() const {
return "sum";
}

float calculate(float x, float y) {
return x + y;
}

~my_plugin_sum() {
std::cout << "Destructing my_plugin_sum ;o)" << std::endl;
}
};

// Exporting `my_namespace::plugin` variable with alias name `plugin`
// (Has the same effect as `BOOST_DLL_ALIAS(my_namespace::plugin, plugin)`)
extern "C" BOOST_SYMBOL_EXPORT my_plugin_sum plugin;
my_plugin_sum plugin;

} // namespace my_namespace

使用boost::dll::importappend_decorations加载插件的简单应用程序:

#include <boost/dll/import.hpp> // for import_alias
#include <iostream>
#include "../tutorial_common/my_plugin_api.hpp"

namespace dll = boost::dll;

int main(int argc, char* argv[]) {

boost::dll::fs::path lib_path(argv[1]); // argv[1] contains path to directory with our plugin library
boost::shared_ptr<my_plugin_api> plugin; // variable to hold a pointer to plugin variable
std::cout << "Loading the plugin" << std::endl;

plugin = dll::import<my_plugin_api>( // type of imported symbol is located between `<` and `>`
lib_path / "my_plugin_sum", // path to the library and library name
"plugin", // name of the symbol to import
dll::load_mode::append_decorations // makes `libmy_plugin_sum.so` or `my_plugin_sum.dll` from `my_plugin_sum`
);

std::cout << "plugin->calculate(1.5, 1.5) call: " << plugin->calculate(1.5, 1.5) << std::endl;
}

该应用程序将输出:

Loading the plugin
Constructing my_plugin_sum
plugin->calculate(1.5, 1.5) call: 3
Destructing my_plugin_sum ;o)

插件中的工厂方法

在前面的示例中,我们从插件导入单个变量。 让我们创建一个使用我们的插件API插件并保持某些状态的类:

#include <boost/dll/alias.hpp> // for BOOST_DLL_ALIAS   
#include "../tutorial_common/my_plugin_api.hpp"

namespace my_namespace {

class my_plugin_aggregator : public my_plugin_api {
float aggr_;
my_plugin_aggregator() : aggr_(0) {}

public:
std::string name() const {
return "aggregator";
}

float calculate(float x, float y) {
aggr_ += x + y;
return aggr_;
}

// Factory method
static boost::shared_ptr<my_plugin_aggregator> create() {
return boost::shared_ptr<my_plugin_aggregator>(
new my_plugin_aggregator()
);
}
};


BOOST_DLL_ALIAS(
my_namespace::my_plugin_aggregator::create, // <-- this function is exported with...
create_plugin // <-- ...this alias name
)

} // namespace my_namespace

如您所见,my_namespace::create_plugin是工厂方法,它创建my_namespace::my_plugin_aggregator的实例。 我们使用BOOST_DLL_ALIAS导出名称为create_plugin的方法。

#include <boost/dll/import.hpp> // for import_alias
#include <boost/function.hpp>
#include <iostream>
#include "../tutorial_common/my_plugin_api.hpp"

namespace dll = boost::dll;

int main(int argc, char* argv[]) {

boost::dll::fs::path shared_library_path(argv[1]); // argv[1] contains path to directory with our plugin library
shared_library_path /= "my_plugin_aggregator";
typedef boost::shared_ptr<my_plugin_api> (pluginapi_create_t)();
boost::function<pluginapi_create_t> creator;

creator = boost::dll::import_alias<pluginapi_create_t>( // type of imported symbol must be explicitly specified
shared_library_path, // path to library
"create_plugin", // symbol to import
dll::load_mode::append_decorations // do append extensions and prefixes
);

boost::shared_ptr<my_plugin_api> plugin = creator();
std::cout << "plugin->calculate(1.5, 1.5) call: " << plugin->calculate(1.5, 1.5) << std::endl;
std::cout << "plugin->calculate(1.5, 1.5) second call: " << plugin->calculate(1.5, 1.5) << std::endl;
std::cout << "Plugin Name: " << plugin->name() << std::endl;
}

在该应用程序中,我们使用boost::dll::import_alias导入了工厂方法。

请注意:creator变量包含对已加载的共享库的引用。 如果此变量超出范围或将被重置,则DLL / DSO将被卸载,并且任何取消引用插件变量的尝试都将导致未定义的行为。

该应用程序的输出如下:

plugin->calculate(1.5, 1.5) call:  3
plugin->calculate(1.5, 1.5) second call: 6
Plugin Name: aggregator

在多个插件中搜索符号

考虑一下情况:我们有多个插件,但是只有其中一些具有我们需要的符号。 让我们编写一个搜索插件列表并尝试查找create_plugin方法的函数。

#include <boost/dll/import.hpp> // for import_alias
#include <boost/make_shared.hpp>
#include <boost/function.hpp>
#include <iostream>
#include "../tutorial_common/my_plugin_api.hpp"

namespace dll = boost::dll;

std::size_t search_for_symbols(const std::vector<boost::dll::fs::path>& plugins) {
std::size_t plugins_found = 0;

for (std::size_t i = 0; i < plugins.size(); ++i) {
std::cout << "Loading plugin: " << plugins[i] << '\n';
dll::shared_library lib(plugins[i], dll::load_mode::append_decorations);
if (!lib.has("create_plugin")) {
// no such symbol
continue;
}

// library has symbol, importing...
typedef boost::shared_ptr<my_plugin_api> (pluginapi_create_t)();
boost::function<pluginapi_create_t> creator
= dll::import_alias<pluginapi_create_t>(boost::move(lib), "create_plugin");

std::cout << "Matching plugin name: " << creator()->name() << std::endl;
++ plugins_found;
}

return plugins_found;
}

如果我们为所有插件调用该方法,我们将获得以下输出:

Loading plugin: "/test/libmy_plugin_aggregator.so"
Matching plugin name: aggregator
Loading plugin: "/test/libmy_plugin_sum.so"
Constructing my_plugin_sum
Destructing my_plugin_sum ;o)

将插件链接到可执行文件

将插件链接到可执行文件具有以下优点:

  • 减少发行的共同规模
  • 简化配置安装
  • 更快的插件加载

让我们从创建可链接的插件开始。 这样的插件将具有一个头文件,该头文件是插件库本身和可执行文件所共有的:

#include <boost/dll/alias.hpp>                          // for BOOST_DLL_ALIAS
#include <boost/shared_ptr.hpp>
#include "../tutorial_common/my_plugin_api.hpp"

namespace my_namespace {
boost::shared_ptr<my_plugin_api> create_plugin(); // Forward declaration
} // namespace my_namespace

BOOST_DLL_ALIAS(
my_namespace::create_plugin, // <-- this function is exported with...
create_plugin // <-- ...this alias name
)

这里的主要技巧是别名定义。 将插件链接到可执行文件时,别名必须在可执行文件的源文件之一中实例化。 否则,链接器将优化我们的插件。

插件的实现如下所示:

#include "static_plugin.hpp" // this is essential, BOOST_SYMBOL_ALIAS must be seen in this file

#include <boost/make_shared.hpp>
#include <iostream>

namespace my_namespace {

class my_plugin_static : public my_plugin_api {
public:
my_plugin_static() {
std::cout << "Constructing my_plugin_static" << std::endl;
}

std::string name() const {
return "static";
}

float calculate(float x, float y) {
return x - y;
}

~my_plugin_static() {
std::cout << "Destructing my_plugin_static" << std::endl;
}
};

boost::shared_ptr<my_plugin_api> create_plugin() {
return boost::make_shared<my_plugin_static>();
}

} // namespace my_namespace

现在,如果我们从源文件中创建一个静态库,并使用以下代码链接该静态库,则可以从插件中导入符号:

#include <boost/dll/shared_library.hpp>         // for shared_library
#include <boost/dll/runtime_symbol_info.hpp> // for program_location()
#include "static_plugin.hpp" // without this headers some compilers may optimize out the `create_plugin` symbol
#include <boost/function.hpp>
#include <iostream>

namespace dll = boost::dll;

int main() {
dll::shared_library self(dll::program_location());

std::cout << "Call function" << std::endl;
boost::function<boost::shared_ptr<my_plugin_api>()> creator
= self.get_alias<boost::shared_ptr<my_plugin_api>()>("create_plugin");

std::cout << "Computed Value: " << creator()->calculate(2, 2) << std::endl;

将插件链接到Linux OS上的可执行文件时,必须使用标志“ -rdynamic”。 否则,从自身加载符号将失败。

运行该程序将输出以下内容:

Call function
Constructing my_plugin_static
Computed Value: 0
Destructing my_plugin_static

如果我们要制作位于单独共享库中的传统插件,我们要做的就是删除#include" static_plugin.hpp"行,并用插件位置和名称替换dll::program_location()

符号遮蔽问题(Linux)

让我们制作一个可执行文件,将一个插件链接到其中,然后尝试加载所有现有的插件:

namespace dll = boost::dll;

class plugins_collector {
// Name => plugin
typedef boost::container::map<std::string, dll::shared_library> plugins_t;

boost::dll::fs::path plugins_directory_;
plugins_t plugins_;

// loads all plugins in plugins_directory_
void load_all();

// Gets `my_plugin_api` instance using "create_plugin" or "plugin" imports,
// stores plugin with its name in the `plugins_` map.
void insert_plugin(BOOST_RV_REF(dll::shared_library) lib);

public:
plugins_collector(const boost::dll::fs::path& plugins_directory)
: plugins_directory_(plugins_directory)
{
load_all();
}

void print_plugins() const;

std::size_t count() const;
// ...
};
int main(int argc, char* argv[]) {

plugins_collector plugins(argv[1]);

std::cout << "\n\nUnique plugins " << plugins.count() << ":\n";
plugins.print_plugins();
// ...

使用默认标志,您将得到一个非常奇怪的输出:

Loaded (0x180db60):"/libs/dll/test/libmy_plugin_aggregator.so"
Constructing my_plugin_static
Destructing my_plugin_static
...

Unique plugins 2:
(0x180db60): static
(0x180e3b0): sum
Destructing my_plugin_sum ;o)

为什么在加载my_plugin_aggregator时构造了my_plugin_static

这是因为libmy_plugin_aggregator.so中的create_plugin函数被其他插件的create_plugin函数遮盖了。 动态链接器认为create_plugin已被加载,因此无需再次加载。

在为POSIX平台进行编译时,请使用“ -fvisibility = hidden”标志(至少对于插件而言)。 此标志使您的代码更具可移植性(Windows下的默认行为是“ -fvisibility = hidden”),减小了二进制文件的大小并缩短了二进制文件的加载时间。

现在,如果我们使用“ -fvisibility = hidden”重新编译您的示例,我们将获得以下输出:

Loaded (0x2406b60):"/libs/dll/test/libmy_plugin_aggregator.so"
Loaded (0x2407410):"/libs/dll/test/libgetting_started_library.so"
Constructing my_plugin_sum
...

Unique plugins 3:
(0x2406b60): aggregator
(0x7fd1cadce2c8): static
(0x24073b0): sum
Destructing my_plugin_sum ;o)

在库卸载时执行回调

Boost.DLL不提供任何现成的机制来捕获库卸载。 但是,这样的任务很容易实现。

您需要做的就是编写一个简单的类,该类存储回调并在销毁时调用它们:

#include <boost/dll/alias.hpp> // for BOOST_DLL_ALIAS
#include <boost/function.hpp>
#include <vector>

namespace my_namespace {

struct on_unload {
typedef boost::function<void()> callback_t;
typedef on_unload this_type;

~on_unload() {
for (std::size_t i = 0; i < callbacks_.size(); ++i) {
callback_t& function = callbacks_[i];
function(); // calling the callback
}
}

// not thread safe
static void add(const callback_t& function) {
static this_type instance;
instance.callbacks_.push_back(function);
}

private:
std::vector<callback_t> callbacks_;
on_unload() {} // prohibit construction outside of the `add` function
};

// Exporting the static "add" function with name "on_unload"
BOOST_DLL_ALIAS(my_namespace::on_unload::add, on_unload)

} // namespace my_namespace

在上面的示例中,my_namespace::on_unload是一个单例结构,其中包含回调向量,并在销毁时调用所有回调。

现在我们可以加载该库并提供回调:

#include <boost/dll/import.hpp>
#include <boost/function.hpp>
#include <iostream>

typedef boost::function<void()> callback_t;

void print_unloaded() {
std::cout << "unloaded" << std::endl;
}

int main(int argc, char* argv[]) {
// argv[1] contains full path to our plugin library
boost::dll::fs::path shared_library_path = argv[1];

// loading library and getting a function from it
boost::function<void(const callback_t&)> on_unload
= boost::dll::import_alias<void(const callback_t&)>(
shared_library_path, "on_unload"
);

on_unload(&print_unloaded); // adding a callback
std::cout << "Before library unload." << std::endl;

// Releasing last reference to the library, so that it gets unloaded
on_unload.clear();
std::cout << "After library unload." << std::endl;
}

如果运行示例,将得到以下输出:

Before library unload.
unloaded
After library unload.

查询库中的符号

我们不知道插件中函数名称的情况可能会发生。 在这种情况下,查询库可能很有用。

想象一下这种情况:我们有一个名为“ Anna”的项目,该项目能够加载和使用包含带有签名void(const std::string&)函数的插件。 我们不知道函数名称,但希望以某种方式找出它们。

解决方案将非常简单。 让我们与插件开发人员保持一致,他们可以按自己的喜好命名函数,但是所有插件函数别名必须位于名为“ Anna”的section:

#include <boost/dll/alias.hpp> // for BOOST_DLL_ALIAS_SECTIONED
#include <iostream>
#include <string>

void print(const std::string& s) {
std::cout << "Hello, " << s << '!' << std::endl;
}

BOOST_DLL_ALIAS_SECTIONED(print, print_hello, Anna)
#include <boost/dll/alias.hpp> // for BOOST_DLL_ALIAS_SECTIONED
#include <string>
#include <iostream>

void print_howdy(const std::string& s) {
std::cout << "How're you doing, " << s << '?' << std::endl;
}

void print_bored(const std::string& s) {
std::cout << "Are you bored, " << s << '?' << std::endl;
}

BOOST_DLL_ALIAS_SECTIONED(print_howdy, howdy, Anna)
BOOST_DLL_ALIAS_SECTIONED(print_bored, are_you_bored, Anna)

现在,我们可以使用boost::dll::library_info轻松获得这些函数:

#include <boost/dll/shared_library.hpp>
#include <boost/dll/library_info.hpp>
#include <iostream>

void load_and_execute(const boost::dll::fs::path libraries[], std::size_t libs_count) {
const std::string username = "User";

for (std::size_t i = 0; i < libs_count; ++i) {
// Class `library_info` can extract information from a library
boost::dll::library_info inf(libraries[i]);

// Getting symbols exported from 'Anna' section
std::vector<std::string> exports = inf.symbols("Anna");

// Loading library and importing symbols from it
boost::dll::shared_library lib(libraries[i]);
for (std::size_t j = 0; j < exports.size(); ++j) {
std::cout << "\nFunction '" << exports[j] << "' prints:\n\t";
lib.get_alias<void(const std::string&)>(exports[j]) // importing function
(username); // calling function
}
}
}

如果运行示例,将得到以下输出:

Function 'print_hello' prints:
Hello, User!

Function 'are_you_bored' prints:
Are you bored, User?

Function 'howdy' prints:
How're you doing, User?

默认情况下,BOOST_DLL_ALIAS宏会将所有别名放入boostdllsection。

高级库引用计数

boost::dll::import的文档中所述,从这些函数返回的变量和函数持有对共享库的引用。 但是,嵌套对象和由import *函数返回的对象不保存对共享库的引用。 在Boost.DLL库级别上无法解决此问题,但是您可以自己解决此问题。 这是一个如何完成此操作的示例。

在此示例中,我们将导入构造插件实例并将该实例绑定到shared_library的函数。

首先,我们需要定义一个新的插件api

#include "../tutorial_common/my_plugin_api.hpp"
#include <boost/dll/config.hpp>

class my_refcounting_api: public my_plugin_api {
public:
// Returns path to shared object that holds a plugin.
// Must be instantiated in plugin.
virtual boost::dll::fs::path location() const = 0;
};

此API与前一个API并没有太大区别。 仅添加了一种抽象方法。

现在让我们定义插件:

#include "refcounting_plugin.hpp"
#include <boost/dll/runtime_symbol_info.hpp> // for this_line_location()

namespace my_namespace {

class my_plugin_refcounting : public my_refcounting_api {
public:
// Must be instantiated in plugin
boost::dll::fs::path location() const {
return boost::dll::this_line_location(); // location of this plugin
}

std::string name() const {
return "refcounting";
}

// ...
};

} // namespace my_namespace

// Factory method. Returns *simple pointer*!
my_refcounting_api* create() {
return new my_namespace::my_plugin_refcounting();
}

该插件与之前的示例没有太大区别,除了调用boost::dll::this_line_locationcreate()函数的其他方法(返回一个简单的指针而不是boost::shared_ptr)之外。

现在,让我们将一个新创建的my_refcounting_api实例绑定到共享库的函数:

#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/dll/shared_library.hpp>

struct library_holding_deleter {
boost::shared_ptr<boost::dll::shared_library> lib_;

void operator()(my_refcounting_api* p) const {
delete p;
}
};

inline boost::shared_ptr<my_refcounting_api> bind(my_refcounting_api* plugin) {
// getting location of the shared library that holds the plugin
boost::dll::fs::path location = plugin->location();

// `make_shared` is an efficient way to create a shared pointer
boost::shared_ptr<boost::dll::shared_library> lib
= boost::make_shared<boost::dll::shared_library>(location);

library_holding_deleter deleter;
deleter.lib_ = lib;

return boost::shared_ptr<my_refcounting_api>(
plugin, deleter
);
}

bind方法中,我们调用plugin->location()。 该调用导致对boost::dll::this_line_location的调用并返回插件位置。 然后,使用make_shared调用创建一个保存shared_libraryshared_ptr

之后,我们用一个library_holding_deleter构造一个boost::shared_ptr<my_refcounting_api>,它保留了共享库的一个实例。

在生产代码中使用std::unique_ptr<my_refcounting_api>而不是my_refcounting_api*可以避免在plugin->location()抛出或其他类引发异常时发生内存泄漏。

就是这样,现在我们可以获取插件实例:

#include <boost/dll/import.hpp>
#include <boost/function.hpp>
inline boost::shared_ptr<my_refcounting_api> get_plugin(
boost::dll::fs::path path, const char* func_name)
{
typedef my_refcounting_api*(func_t)();
boost::function<func_t> creator = boost::dll::import_alias<func_t>(
path,
func_name,
boost::dll::load_mode::append_decorations // will be ignored for executable
);

// `plugin` does not hold a reference to shared library. If `creator` will go out of scope,
// then `plugin` can not be used.
my_refcounting_api* plugin = creator();

// Returned variable holds a reference to
// shared_library and it is safe to use it.
return bind( plugin );

// `creator` goes out of scope here and will be destroyed.
}

这是main()函数的样子:

// Runtime plugin load
#include <iostream>
#include "refcounting_api.hpp"

int main(int argc, char* argv[]) {

boost::shared_ptr<my_refcounting_api> plugin = get_plugin(
boost::dll::fs::path(argv[1]) / "refcounting_plugin",
"create_refc_plugin"
);

std::cout << "Plugin name: " << plugin->name()
<< ", \nlocation: " << plugin->location()
<< std::endl;
}
// Plugin name: refcounting,
// location: "/libs/dll/librefcounting_plugin.so"
// Plugin was linked in
#include <boost/dll/runtime_symbol_info.hpp> // program_location()
#include <iostream>
#include "refcounting_plugin.hpp"

int main() {
boost::shared_ptr<my_refcounting_api> plugin = get_plugin(
boost::dll::program_location(),
"create_refc_plugin"
);

std::cout << "Plugin name: " << plugin->name()
<< ", \nlocation: " << plugin->location()
<< std::endl;
}

// Plugin name: refcounting,
// location: "/tutorial8_static"

从Windows dll导入C函数

这是一个简单的例子,但是它有一个棘手的地方。 当您以其名称导入C函数时,必须使用boost::dll::import,而不是boost::dll::import_alias

#include <boost/dll/import.hpp>         // for dll::import
#include <boost/dll/shared_library.hpp> // for dll::shared_library
#include <boost/function.hpp>
#include <iostream>
#include <windows.h>

namespace dll = boost::dll;

int main() {
typedef HANDLE(__stdcall GetStdHandle_t)(DWORD ); // function signature with calling convention

// OPTION #0, requires C++11 compatible compiler that understands GetStdHandle_t signature.

auto get_std_handle = dll::import<GetStdHandle_t>(
"Kernel32.dll",
"GetStdHandle",
boost::dll::load_mode::search_system_folders
);
std::cout << "0.0 GetStdHandle() returned " << get_std_handle(STD_OUTPUT_HANDLE) << std::endl;

// You may put the `get_std_handle` into boost::function<>. But boost::function<Signature> can not compile with
// Signature template parameter that contains calling conventions, so you'll have to remove the calling convention.
boost::function<HANDLE(DWORD)> get_std_handle2 = get_std_handle;
std::cout << "0.1 GetStdHandle() returned " << get_std_handle2(STD_OUTPUT_HANDLE) << std::endl;


// OPTION #1, does not require C++11. But without C++11 dll::import<> can not handle calling conventions,
// so you'll need to hand write the import.
dll::shared_library lib("Kernel32.dll", dll::load_mode::search_system_folders);
GetStdHandle_t& func = lib.get<GetStdHandle_t>("GetStdHandle");

// Here `func` does not keep a reference to `lib`, you'll have to deal with that on your own.
std::cout << "1.0 GetStdHandle() returned " << func(STD_OUTPUT_HANDLE) << std::endl;

return 0;
}

Mangled Import

本节介绍了允许从dll导入mangled符号的实验功能。 尽管此功能是该库的独有功能,并且看起来很有希望,但尚未经过全面测试,因此认为不稳定。

作为一个简短的示例,我们可以很容易地导入以下函数:

//library.dll
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 ABIMSVC ABIMSVC ABI需要boost.spirit.x3支持,仅允许使用MSVC2015Itanium API需要C ++ 11。

  • GCC
  • Clang
  • MSVC 2015
  • Intel C ++

Itanium API不会导入函数的返回类型,也不会导入全局变量的类型。

Mangled Import示例

Mangled Import的核心是smart_library类。 它可以以整齐的形式导入函数和变量; 为此,smart_library会读取库的整个outline,并对其中的每个入口点进行demangles 。 这也意味着该类只能构造一次。

为了将所有方法导入下面的库中,我们将使用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> // for import_alias
#include <iostream>
#include <memory>

namespace dll = boost::dll;

struct alias;

int main(int argc, char* argv[]) {

boost::dll::fs::path lib_path(argv[1]); // argv[1] contains path to directory with our plugin library
dll::smart_lib lib(lib_path); // smart library instance

为了创建类,我们将需要分配内存。 当然,这意味着我们需要知道大小。 不幸的是,它没有导出到dll中,因此我们为导出添加了静态大小函数。 静态用作普通函数。

因此,我们将其导入,调用并分配内存。

auto size_f = lib.get_function<std::size_t()>("space::my_plugin::size"); //get the size function

auto size = size_f(); // get the size of the class
std::unique_ptr<char[], size> buffer(new char[size]); //allocate a buffer for the import
alias & inst = *reinterpret_cast<alias*>(buffer.get()); //cast it to our alias type.

现在,我们有了内存大小和带有别名类型的引用。 为了使用它,我们需要将类型注册为别名。 这将使智能库可以解析类型名称。

lib.add_type_alias("space::my_plugin"); //add an alias, so i can import a class that is not declared here

为了使用该类,我们当然需要对其进行初始化,即调用构造函数。 Itanium ABI也可以实现分配构造函数。 这就是构造函数可能具有两个函数的原因。 因为我们已经分配了内存,所以我们使用标准构造函数版本,即字符串构造函数。 因此,我们通过传递签名来选择构造函数。

auto ctor = lib.get_constructor<alias(const std::string&)>(); //get the constructor
ctor.call_standard(&inst, "MyName"); //call the non-allocating constructor. The allocating-constructor is a non-portable feature

因此,由于该类现在已初始化,因此我们可以调用name方法。 如果函数是const和/或volatile,则作为type传递的type参数必须具有相同的限定符。

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;

重载的函数只能单独导入。

//import both calculate functions
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>(); //get the destructor
dtor.call_standard(&inst);
std::cout << "plugin->calculate(1.5, 1.5) call: " << plugin->calculate(1.5, 1.5) << std::endl;
}

类导入

现在演示了如何导入mangled和方法。 但是,这是一种相当通用的方式,因此提供了更简单的接口,该接口还允许访问对象的type_info。

我们将采用相同的类并导入相同的方法,但要使用导入功能。

我们将库放入shared_pointer中,因为每次导入都将包含指向它的指针。 也就是说,我们不想复制它。

#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::dll::fs::path lib_path(argv[1]); // argv[1] contains path to directory with our plugin library
smart_library lib(lib_path);// smart library instance

与前面的示例类似,我们需要类的大小。

auto size_f = import_mangled<std::size_t()>("space::my_plugin::size"); //get the size function

auto size = (*size_f)(); // get the size of the class

附带一提,我们还可以使用该函数轻松导入变量。

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;

此外,我们可以像这样访问typeinfo。

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), //not qualified
const alias, f(int), f(), //const
volatile alias, f(), f(int), //volatile
const volatile alias, f(double)//const volatile
>(lib, "f");

误用

典型的错误和误用位于本节中。 请仔细阅读,这样可以节省大量的调试时间!

问题:程序在delete或free()时崩溃。

修复:您的插件和程序必须使用相同的Standard C ++和C库,两者都必须动态链接。 对于Visual Studio,请使用/MD或/MDd编译器开关。

问题:程序在catch(...)块中崩溃。

例:

try {
auto f = dll::import<int()>(path_to_pugin, "function");
f();
// `f` goes out of scope
} catch (const std::exception& e) {
std::cerr << e.what();
}

修复:异常是在插件内部生成的,因此它引用插件中的异常代码。 当f超出范围时,将卸载该插件,并且对异常代码的引用将被破坏。 任何使用异常变量的尝试都可能使用悬挂引用,从而导致分段错误。 修正您的代码:

auto f = dll::import<int()>(path_to_pugin, "function");
try {
f();
// `f` goes out of scope
} catch (const std::exception& e) {
std::cerr << e.what();
}

问题:线程本地存储似乎已损坏。

修复:某些平台对使用TLS的插件(例如Windows)没有开箱即用的支持。 使用特定于平台的解决方法,或者仅在插件中不使用TLS。

问题:尝试调用已加载的函数会导致崩溃或函数返回错误的结果。

修复:Boost.DLL不保证ABI稳定性。 如果您使用不同的编译器或不同的编译器开关来编译插件和程序,则函数ABI可能会更改,并且您将得到不正确的代码。

问题:插件卸载后程序崩溃。

例:

void foo() {
shared_ptr<int> p;
try {
auto f = dll::import<shared_ptr<int>()>(path_to_pugin, "function");
p = f();
// `f` goes out of scope
} catch (const std::exception& e) {
std::cerr << e.what();
}
std::cout << *p;
// crashes here
}

修复:在该特定示例中,问题出在shared_ptr<int>内。 它保留一个类型已删除的删除器,该删除器的代码位于插件中。 在销毁p时,shared_ptr<int>尝试调用该删除程序,但是该插件已经卸载,并且删除程序的代码不再可用。 经验法则::如果您的插件方法返回任何C++类变量,请确保已加载插件,直到该变量及其任何副本在范围内为止。 导致此类错误的典型类是:

  • any
  • function
  • shared_ptr
  • 任何具有多态分配器的容器
  • std :: type_index
  • std :: type_info
  • std :: exception_ptr
  • std :: unique_ptr <Base>持有从插件派生的类型
  • 插件抛出的异常类

参考

共享库参考

共享库可引用参考

局限性

导出弱符号(在MinGW,Android上失败)

用户定义的节名称(在SunOS + Oracle Solaris Studio编译器上失败)

线程安全库加载(在FreeBSD,MacOS,iOS等上失败)

嵌套函数定义

常问问题

设计原理

跨编译器的ABI可移植性

用户的插件API

性能和内存分配

自加载

别名与混乱

依存关系

修订记录

致谢