...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
This section describes the experimental feature of allowing the import of mangled symbols from an dll. While this feature is unique to this library and looks quite promising, it is not throroughly tested and thus not considered stable.
As a short example we can import the following functions quite easily:
//library.dll namespace foo { int bar(int); double bar(double); }
And the import looks like this:
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;
Currently, the Itanium ABI and the MSVC ABI are implemented. The MSVC ABI requires boost.spirit.x3 support, allowing only the usage of MSVC 2015. The Itanium API requires C++11.
The Itanium API does not import the return type of functions, nor the type of global variables.
The core of the mangled import is the smart_library
class. It can import functions and variables in their mangled form; to do
this, the smart_library reads the entire outline of the library and demangles
every entry point in it. That also means, that this class should only be
constructed once.
In order to import all the methods in the following library, we will use
the smart_library
.
The first thing to do when creating your own plugins is define the plugin interface. There is an example of an abstract class that will be our plugin 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; }; }
Alright, now we have the definition for the plugin, so we use it in the following
full-fleshed example. Mind that there is a more convenient solution to import
member-functions which will be discussed later on. This example shows however
what the smart_lib
provides as features.
At first we setup the smart library. Mind that the alias class is needed to provide a type-alias for the 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
In order to create the class, we will need to allocate memory. That of course means, that we need to know the size; unfortunately it is not exported into the dll, so we added the static size function for export. Static are used as plain functions.
So we import it, call it and allocate memory.
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.
Now, we have the memory size and a reference with our alias type. In order to use it, we need to register the type as an alias. That will allow the smart library to resolve the type name.
lib.add_type_alias("space::my_plugin"); //add an alias, so i can import a class that is not declared here
In order to use the class, we of course need to initialize it, i.e. call the constructor. The Itanium ABI may also implement an allocating constructor. That is why a constructor may have two functions; since we already have allocated the memory we use the standard constructor version, of the constructor from string. So we select the constructor by passing the signature.
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
So since the class is now initialized, we can call the name method. If the function is const and/or volatile the type parameter passed as type must have the same qualifiers.
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;
Overloaded functions can only be imported separately.
//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;
Import of static variable is done like with plain variable.
auto & var = lib.get_variable<int>("space::my_plugin::value"); cout << "value " << var << endl;
Since we are finished, we call the destructor of the class.
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; }
Now it is demonstrated, how mangled and methods may be imported. This is however a rather versatile way, so an easier interface is provided, which also allows access to the type_info of an object.
We will take the same class and import the same methods, but do it with the import features.
We put the library into a shared_pointer, because every import will hold such a pointer to it. That is, we do not want to copy it.
#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
Similar to the previous example, we need the size of the class.
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
On a side note, we can also import variable easily with that function.
auto value = import_mangled<int>(lib, "space::my_plugin::value");
We do the forward declaration on the first call, and invoke the constructor directly. This is quite simple and allows to invoke the constructor directly. The destructor will be invoked automatically.
auto cl = import_class<class alias, const std::string&>(lib, "space::my_plugin::some_class", size, "MyName");
Invoking a function will still require to import it first.
auto name = import_mangled<const alias, std::string()>(lib, "name"); std::cout << "Name: " << (cl->*name)() << std::endl;
For overloaded functions, we can import them as groups, which will give us an object containing the overloads.
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;
Additionally, we can access the typeinfo like this.
std::type_info &ti = cl.get_type_info();
Not handled in the example was the question, of how it is handled if the qualification differs for an overloaded function. This can be done, by passing the class again with another qualification - a function signature will always pick the last one provided.
If we have this in our plugin:
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; };
we can import them all at once, with the following command:
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");