=================================== How-To write cross-platform plugins =================================== Sometimes we need to extend the functionality of our program at run-time. OS APIs for this let you load shared libraries, and then find symbols within them by name. This works well for C symbols, but when it comes to C++, things start to break down. So instead of trying to deal with this in a complicated manner that takes into account symbol mangling across compilers and platforms, the typical approach is to return a pointer from the plugin to some data structure that we can use to access the symbols we want. In practice, though, this is a `void *` that the caller of the plugin code needs to cast to whichever data structure it represents. So for each plugin, you also need some header file that defines the type(s) to cast to. Treat it like an interface. Because of this approach of casting a `void *`, we'd like a run-time check to see if the cast is reasonably safe. For that, we embed a "plugin type" in the plugin. And because interfaces can change even when the type of plugin does not, we also add a plugin version. As the plugin interface, you could use a class with pure virtual functions, or take a more manual approach. We'll do the latter here just to demonstrate it. .. sourcecode:: cpp :linenos: :dedent: :caption: Plugin code (plugin.h) #include #include namespace bar { using foo_function = std::function; struct my_interface { foo_function foo; // TODO you can add as many things as you'd like. }; } // namespace bar With this plugin interface, you can now define the plugin code: .. sourcecode:: cpp :linenos: :dedent: :caption: Plugin code (plugin.cpp) #include #include "plugin.h" namespace bar { // Some implementation of foo_function void plugin_foo(std::string const &) { // do something } // We need an instance of my_interface, and might as well make it a global // variable. my_interface interface = { plugin_foo }; // With the interface declared, let's create plugin metadata. liberate::sys::plugin_meta meta = { 42, // The user-defined plugin/interface type 1, // The user-defined version of this interface type &interface, // A reference to the interface data nullptr, // Always empty. }; } // namespace bar // We cannot quite escape Window's need to declare exported functions, // but that's alright. #if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) #define PLUGIN_EXPORT __declspec(dllexport) #else #define PLUGIN_EXPORT #endif extern "C" { // The main plugin interface is a C function named "liberate_plugin_meta" // that returns a liberate::sys::plugin_meta instance. Note that it would // also be possible to dynamically allocate things, but the global variable // is actually easier here. PLUGIN_EXPORT void * liberate_plugin_meta() { return &bar::meta; } } // extern "C" As you can see, the amount of metadata to manage in the plugin code is not particularly much. We'll leave compilation of the plugin code out of this guide, because it depends on your build system. But if you're using `meson`, you might want to look at the `shared_module`_ reference entry. .. _shared_module: https://mesonbuild.com/Reference-manual_functions.html#shared_module What's still missing is how to use the plugin in practice. .. sourcecode:: cpp :linenos: :dedent: :caption: Plugin usage #include #include "plugin.h" using namespace liberate::sys; plugin_meta const * meta = nullptr; // We need a pointer to the metadata structure. If an appropriate function // was found, and it yielded a pointer, then the meta structure will now // be filled. auto success = load_plugin(meta, "path/to/plugin.so"); // We only know how to deal with the plugin type '42' in version '1' here. if (meta.type != 42 || meta.version != 1) { exit(1); } // The plugin interface is of type my_interface. auto iface = static_cast(meta->data); // And we can now invoke functions in it, etc. iface->foo("Hello, world!"); Note that the `meta->plugin_handle` field is now set; you initialized it to a `nullptr` in the plugin code. It contains a handle to the loaded plugin in a platform dependent format. This then allows you to unload the plugin again. Unloading also frees the metadata structure. .. sourcecode:: cpp :linenos: :dedent: :caption: Unload plugin unload_plugin(meta); assert(meta == nullptr); Managing the installation location of plugins, etc. is not in scope for this library.