This is a snapshot of a work-in-progress documentation theme. If you feel lost, miss something, or want to see the original Doxygen docs instead, head over to the archived version of this documentation page.

Examples » Plugin management

Developing, loading and using plugins.

The PluginManager::Manager class provides hierarchical plugin management functions. Main features:

  • Plugin manager version and plugin interface version checks to avoid unexpected behavior
  • Both static and dynamic plugin support
  • Plugin dependecies and aliases
  • Usage checks, 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 well-defined virtual methods, which defines the way how to work with a particular plugin.

Every plugin interface must be derived from PluginManager::AbstractPlugin and must reimplement the PluginManager::AbstractPlugin constructor. This is needed for instance use checks, as described above. Plugin classes derived from that interface must reimplement the constructor too, of course.

Additionaly, an interface string must be defined with CORRADE_PLUGIN_INTERFACE() in the header 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.

class AbstractAnimal: public PluginManager::AbstractPlugin {
    CORRADE_PLUGIN_INTERFACE("cz.mosra.Corrade.Examples.AbstractAnimal/1.0")

    public:
        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 one class, which is derived from one plugin interface. The plugin class in then registered as static or dynamic plugin. Every plugin must also have its configuration file, which contains information about plugin dependencies and optionally also plugin-specific data. Full specification of plugin configuration file syntax can be found in PluginManager::PluginMetadata class documentation.

First we define one static plugin, which will be included in the application out-of-the-box. After declaring that plugin we register it with the CORRADE_PLUGIN_REGISTER() macro. The first argument is plugin name (which will be used when instancing the plugin), second argument is name of the plugin class and third is the name of used plugin interface.

class Canary: public AbstractAnimal {
    public:
        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; }
};
CORRADE_PLUGIN_REGISTER(Canary, Corrade::Examples::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 plugin manager.

class Dog: public AbstractAnimal {
    public:
        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, Corrade::Examples::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 Corrade::PluginManager)
endif()
corrade_add_static_plugin(Canary ${CMAKE_CURRENT_BINARY_DIR} Canary.conf Canary.cpp)

The corrade_add_plugin() macro takes plugin name as first argument, second argument is a directory where to install the plugin files, third argument is name of configuration file and after that comes one or more source files. We use the build directory for storing the plugins to avoid the need for installation.

The corrade_add_static_plugin() is similar to the above macro, except that it creates a static plugin instead of a dynamic one.

Plugin management

Now it's time to initialize PluginManager::Manager and make use of the plugins. PluginManager::Manager is a templated class and that means it will load and make available only plugins with the interface specified as template.

In order to make the plugin manager find the static plugins, we have to import them with a macro CORRADE_PLUGIN_IMPORT() (for example at the beginning of main function), which takes plugin name as 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::Arguments.

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")
        .addOption("plugin-dir", ".").setHelp("plugin-dir", "plugin directory to use", "DIR")
        .setHelp("Displays info about given animal.")
        .parse(argc, argv);

    /* Initialize plugin manager with given directory */
    PluginManager::Manager<Examples::AbstractAnimal> manager{args.value("plugin-dir")};

    /* 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.instance(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 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-dir DIR] [--] plugin

Displays info about given animal.

Arguments:
  plugin            animal plugin name
  -h, --help        display this help message and exit
  --plugin-dir DIR  plugin directory to use
                    (default: .)

$ ./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.