Plugin management
Developing, loading and using plugins.
The PluginManager::
- Plugin manager version and plugin interface version checks to avoid unexpected behavior
- Both static and dynamic plugin support
- Plugin dependecies and aliases
- Usage checks, the manager doesn't allow plugin unload if there are any active plugin instances
This tutorial will give you a brief introduction into how plugins are defined, compiled and managed.
Plugin interface
Plugin interface is a class with virtual methods, defining the way how to work with a particular plugin.
Every plugin interface must be derived from PluginManager::
Additionaly, an interface string must be provided by a pluginInterface()
function to make sure loaded plugins depend on the same interface in the same version. The macro takes a string which uniquely names that particular interface. A good practice is to use "Java package name"-style syntax because this makes the name as unique as possible. The interface name should also contain version identifier to make sure the plugin will not be loaded with incompatible interface version.
To make lives of the users easier, we can define a list of paths where the plugins will be searched for using pluginSearchPaths(). The paths can be either absolute (for example hardcoding a system-wide installation path) or relative (relative to the executable file). In this case the plugins will be right next to the executable, so just a single entry with ""
will do. If we wouldn't specify the search paths, the user would need to pass a plugin search path to the plugin manager constructor.
class AbstractAnimal: public PluginManager::AbstractPlugin { public: static std::string pluginInterface() { return "cz.mosra.corrade.Examples.AbstractAnimal/1.0"; } static std::vector<std::string> pluginSearchPaths() { return {""}; } explicit AbstractAnimal(PluginManager::AbstractManager& manager, const std::string& plugin): AbstractPlugin{manager, plugin} {} virtual std::string name() const = 0; virtual int legCount() const = 0; virtual bool hasTail() const = 0; };
Plugin definition
Every plugin is represented by a class derived from a particular plugin interface. The plugin class in then registered as a static or dynamic plugin. Every plugin must also have its metadata file, which contains information about plugin dependencies and optionally also plugin-specific data. Full specification of plugin metadata file syntax can be found in the PluginManager::
First we define one static plugin, which will be included in the application out-of-the-box:
class Canary: public AbstractAnimal { public: explicit Canary(PluginManager::AbstractManager& manager, const std::string& plugin): AbstractAnimal{manager, plugin} {} std::string name() const override { return "Achoo"; } int legCount() const override { return 2; } bool hasTail() const override { return true; } };
After defining the plugin we have to register it with the CORRADE_
CORRADE_PLUGIN_REGISTER(Canary, Canary, "cz.mosra.corrade.Examples.AbstractAnimal/1.0")
And a corresponding configuration file, Canary.conf
:
[data] name=I'm allergic to canaries!
Then we define one dynamic plugin. Note that the macro for registering dynamic plugin is the same, the only difference will be in CMakeLists.txt
, as you will see below. This way you can decide at compile time which plugins will be dynamic, which will be static, or, for example, which will be compiled directly into the library/application, so they can be used directly without the plugin manager.
class Dog: public AbstractAnimal { public: explicit Dog(PluginManager::AbstractManager& manager, const std::string& plugin): AbstractAnimal{manager, plugin} {} std::string name() const override { return "Doug"; } int legCount() const override { return 4; } bool hasTail() const override { return true; } };
CORRADE_PLUGIN_REGISTER(Dog, Dog, "cz.mosra.corrade.Examples.AbstractAnimal/1.0")
And a corresponding configuration file, Dog.conf
:
[data] name=A simple dog plugin
Plugin compilation
Requiring the Corrade package using find_package()
will define two useful macros for plugin compilation:
find_package(Corrade REQUIRED PluginManager) set_directory_properties(PROPERTIES CORRADE_USE_PEDANTIC_FLAGS ON) corrade_add_plugin(Dog ${CMAKE_CURRENT_BINARY_DIR} "" Dog.conf Dog.cpp) if(CORRADE_TARGET_WINDOWS) target_link_libraries(Dog PRIVATE Corrade::PluginManager) endif() corrade_add_static_plugin(Canary ${CMAKE_CURRENT_BINARY_DIR} Canary.conf Canary.cpp)
The corrade_
The corrade_
Plugin management
Now it's time to initialize PluginManager::
In order to make the plugin manager find the static plugins, we have to import them with the CORRADE_main()
function). It takes a plugin name as an argument.
This example application will load plugin specified as command-line argument and then displays brief info about a given animal. For convenient argument parsing and usage documentation we used Utility::
int main(int argc, char** argv) { /* Import static plugin using the same name as in Canary.cpp */ CORRADE_PLUGIN_IMPORT(Canary) Utility::Arguments args; args.addArgument("plugin").setHelp("plugin", "animal plugin name") .setHelp("Displays info about given animal.") .parse(argc, argv); /* Initialize plugin manager with given directory */ PluginManager::Manager<Examples::AbstractAnimal> manager; /* Try to load a plugin */ if(!(manager.load(args.value("plugin")) & PluginManager::LoadState::Loaded)) { Utility::Error{} << "The requested plugin" << args.value("plugin") << "cannot be loaded."; return 2; } /* Instance of an animal */ std::unique_ptr<Examples::AbstractAnimal> animal = manager.instantiate(args.value("plugin")); Utility::Debug{} << "Using plugin" << '\'' + animal->metadata()->data().value("name") + '\'' << "...\n"; Utility::Debug{} << "Name: " << animal->name(); Utility::Debug{} << "Leg count:" << animal->legCount(); Utility::Debug{} << "Has tail: " << (animal->hasTail() ? "yes" : "no"); return 0; }
Compile the application with a simple CMake add_executable()
command and don't forget to link in all the static plugins compiled above:
add_executable(PluginTest main.cpp) target_link_libraries(PluginTest PRIVATE Canary Corrade::PluginManager)
After a successful compilation we can run the application with plugin name as an argument:
$ ./PluginTest --help Usage: ./PluginTest [-h|--help] [--] plugin Displays info about given animal. Arguments: plugin animal plugin name -h, --help display this help message and exit $ ./PluginTest Canary Using plugin 'I'm allergic to canaries!' Name: Achoo Leg count: 2 Has tail: yes $ ./PluginTest Dog Using plugin 'A simple dog plugin' Name: Doug Leg count: 4 Has tail: yes
The full file content is linked below. Full source code is also available in the GitHub repository.