Boost.DLL
动机
在运行时将特定功能添加到现有软件应用程序可能会很有用。 此类扩展或插件通常使用在运行时加载的动态库模块(DLL,SO/DSO)来实现。
该库旨在以可移植的跨平台方式简化使用C ++的插件开发。
库提供了一种跨平台的可移植方式:
- 加载库
- 导入任何本地函数和变量
- 为C ++ mangled函数和符号创建别名
- 查询库中的节和导出的符号
- 自加载和自查询
- 通过导出的符号获取程序和模块位置
入门
Boost.DLL
是header-only
库。 要开始使用该库,您只需要包含<boost/dll.hpp>
头文件。 之后,您可以自由导入和导出函数和变量。 导入代码需要链接到boost_filesystem
和boost_system
库。
如果要加载库,只需构造boost::dll::shared_library
类,并将库的路径作为参数:
boost::dll::shared_library lib("/test/boost/application/libtest_library.so");
现在,您可以使用get
和get_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_location
,boost::dll::this_line_location
和boost::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::import
和append_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
宏会将所有别名放入boostdll
section。
高级库引用计数
如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_location
和create()
函数的其他方法(返回一个简单的指针而不是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_library
的shared_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 ABI
和MSVC ABI
。 MSVC ABI
需要boost.spirit.x3
支持,仅允许使用MSVC2015
。Itanium 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>
持有从插件派生的类型- 插件抛出的异常类