Guides
Write a plugin
This guide shows how to write a plugin, build it as a shared library and load it into a device with -P. The result
is a device that loads your plugin at startup and accepts the options of the plugin on its command line.
Before you start
Section titled “Before you start”- Install FairMQ. See Install.
- Have a device that starts with
runDevice.h, for examplefairmq-sinkor a device of the examples. - Read Plugins and control for the plugin system and the controller role.
A plugin has four parts:
- A class that derives from
fair::mq::Plugin. - A function that declares the options of the plugin. This part is optional.
- The macro
REGISTER_FAIRMQ_PLUGIN. It exports the plugin from the library. - A shared library with the name
libfairmq-plugin-<name>.so.
A plugin can also be part of the executable. See Put the controller in your program.
The code samples on this page come from the test plugin test_dummy (test/helper/plugins/dummy.h.in) and from the
built-in control plugin.
Write the plugin class
Section titled “Write the plugin class”-
Include
<fairmq/Plugin.h>. Derive your class fromfair::mq::Plugin. -
Give the class a constructor with the five parameters of the
Pluginconstructor. These are the name, the version, the maintainer, the homepage and a pointer toPluginServices. Pass them to the base class. The macroREGISTER_FAIRMQ_PLUGINcalls this constructor. -
In the constructor, read your options. Then subscribe to the events that you need, for example to the state changes.
-
Remove your subscriptions before the plugin goes away.
DeviceRunnerdestroys the plugins before the device. The test plugin unsubscribes in the stateEXITING. The built-in plugins unsubscribe in their destructors.
class DummyPlugin : public fair::mq::Plugin{ public:
DummyPlugin( const std::string name, const Version version, const std::string maintainer, const std::string homepage, PluginServices* pluginServices) : Plugin(name, version, maintainer, homepage, pluginServices) { SubscribeToDeviceStateChange( [&](DeviceState newState){ switch (newState) { case DeviceState::Exiting: UnsubscribeFromDeviceStateChange(); break; default: break; } } ); }}; /* class DummyPlugin */The base class gives the plugin API: the state, the controller role, the properties and the log settings. The list is in What a plugin can do.
Add options
Section titled “Add options”-
Write a function without parameters that returns
fair::mq::Plugin::ProgOptions. This type isboost::optional<boost::program_options::options_description>. -
Fill an
options_descriptionwith your options and return it. The caption of the description is the heading of the group in the output of--help. -
For a plugin without options, use
fair::mq::Plugin::NoProgramOptionsin the macro.
auto DummyPluginProgramOptions() -> Plugin::ProgOptions{ auto plugin_options = boost::program_options::options_description{"Dummy Plugin"}; plugin_options.add_options() ("custom-dummy-option", boost::program_options::value<std::string>(), "Cool custom option.") ("custom-dummy-option2", boost::program_options::value<std::string>(), "Another cool custom option."); return plugin_options;}The device adds these options to its command line. After the parsing, each option is a property with the same name. Read it in the constructor of the plugin:
// An option without a default value is a property only if the command line gives it.auto const value = GetProperty<std::string>("custom-dummy-option", "nothing");GetProperty<T>(key) without a fallback value throws
fair::mq::PropertyNotFoundError when the property does not exist. The type T must be the type of the option, for
example float for --rate.
Register the plugin
Section titled “Register the plugin”Write the macro REGISTER_FAIRMQ_PLUGIN once, at namespace scope. The built-in control plugin registers itself so:
REGISTER_FAIRMQ_PLUGIN( Control, // Class name control, // Plugin name (string, lower case chars only) (Plugin::Version{FAIRMQ_VERSION_MAJOR, FAIRMQ_VERSION_MINOR, FAIRMQ_VERSION_PATCH}), // Version "FairRootGroup <fairroot@gsi.de>", // Maintainer "https://github.com/FairRootGroup/FairMQ", // Homepage ControlPluginProgramOptions // Free function which declares custom program options for the // plugin signature: () -> // boost::optional<boost::program_options::options_description>)| Argument | Meaning |
|---|---|
KLASS |
The plugin class. |
NAME |
The name of the plugin, without quotes. Give this name to -P. The file name of the library ends with it: libfairmq-plugin-NAME.so. The macro makes function names from it, so use lower-case letters and _, for example test_dummy. |
VERSION |
A fair::mq::tools::Version with the major, minor and patch number. Put it in parentheses, (fair::mq::Plugin::Version{1, 0, 0}): the commas in the braces otherwise separate macro arguments. See Version.h. |
MAINTAINER |
A string, for example a name and an e-mail address. |
HOMEPAGE |
A string with a URL. |
PROGOPTIONS |
The function that declares the options, or fair::mq::Plugin::NoProgramOptions. |
The macro defines a factory function and exports two symbols: make_NAME_plugin and get_NAME_plugin_progoptions.
The plugin manager looks for these symbols when it loads the plugin
(Plugin.h).
Build the plugin
Section titled “Build the plugin”Build the plugin as a shared library. The name of the CMake target is fairmq-plugin- and the plugin name. CMake
then makes the file libfairmq-plugin-<name>.so.
cmake_minimum_required(VERSION 3.15)project(MyPlugin CXX)
find_package(FairMQ REQUIRED)# FairMQConfig.cmake does not find the dependencies of FairMQ. Find them here.foreach(dep IN LISTS FairMQ_PACKAGE_DEPENDENCIES) find_package(${dep} ${FairMQ_${dep}_VERSION} REQUIRED COMPONENTS ${FairMQ_${dep}_COMPONENTS})endforeach()
add_library(fairmq-plugin-myplugin SHARED myplugin.cxx)target_link_libraries(fairmq-plugin-myplugin PRIVATE FairMQ::FairMQ)In this sample, myplugin.cxx contains the class, the options function and the macro with the name myplugin. The
target FairMQ::FairMQ gives the include paths, the dependencies and C++17. Without the loop over
FairMQ_PACKAGE_DEPENDENCIES, CMake does not find the targets of the dependencies. The prefix FairMQPlugin_ also works:
the tests of FairMQ build FairMQPlugin_test_dummy and fairmq-plugin-test_dummy2. See
Use FairMQ in a CMake project for find_package().
cmake -S . -B buildcmake --build buildLoad the plugin
Section titled “Load the plugin”-
Give the name of the plugin to
-P. Give-Ponce for each plugin. -
Give the directory of the library to
-S, or put it inLD_LIBRARY_PATH. -
Start the device. This command uses the built-in device
fairmq-sink:Terminal window fairmq-sink --id sink1 \--channel-config name=data,type=pull,method=bind,address=tcp://*:5555 \--control static --severity debug \-P myplugin -S ./build -
Stop the device with Ctrl-C.
To load a plugin from your own main(), call LoadPlugin() in the runner hook hooks::LoadPlugins. The
custom-controller example does this for a plugin that the executable contains.
To load plugins without DeviceRunner, see
Load plugins without DeviceRunner.
Check the result
Section titled “Check the result”-
--helpwith-P myplugin -S ./buildprints the options of the plugin, under the caption of its options group. The device does not start with--help. -
With
--severity debug, the device logs a line for each plugin that it creates, and a line when it destroys the plugin:Loaded plugin: 'myplugin', version '1.0.0', maintainer 'Jane Doe <jane.doe@example.org>', homepage 'https://example.org/myplugin' -
With
--severity debug, the tableConfiguration:lists the options of the plugin that have a value.
Use the plugin API
Section titled “Use the plugin API”Read and change properties
Section titled “Read and change properties”SetProperty<T>()adds or changes a property. It notifies all subscribers.UpdateProperty<T>()changes only a property that exists. It returnsfalseotherwise.- Give the type for text values:
SetProperty<std::string>("key", "value"). Without it, the property gets the typeconst char*, andGetProperty<std::string>()throws an exception.
The time when a change has an effect depends on the property. See Control a device from code.
Follow the state of the device
Section titled “Follow the state of the device”SubscribeToDeviceStateChange() calls your function at the
start of each new state, in the thread of the state machine. The device waits until your function returns. For long
work, start a thread in the plugin and stop it in the destructor. The control plugin runs its controller in this way.
Control the device
Section titled “Control the device”A plugin that calls TakeDeviceControl() in its constructor gets the
controller role before the control plugin. The plugins of -P are created first. See
Control a device from code.
Change the log settings
Section titled “Change the log settings”CycleLogConsoleSeverityUp() and the other CycleLog functions
change the console severity and the verbosity in steps. To set a value, change the property, for example
SetProperty<std::string>("severity", "debug"). See Logging.
Problems
Section titled “Problems”| Problem | Cause and solution |
|---|---|
Could not load dynamic plugin myplugin from configured search paths (-S) nor with system lookup … |
The plugin manager found no file, or the file does not load. Check the file name libfairmq-plugin-myplugin.so, the directory in -S or LD_LIBRARY_PATH, and the name in REGISTER_FAIRMQ_PLUGIN. |
… is not a directory. |
A path of -S is a file. Give the directory. |
An error occurred while instantiating plugin myplugin: … |
The constructor of the plugin throws, for example Config has no key: … from GetProperty<T>(). Give a fallback value, or check with PropertyExists() first. |
Plugin 'myplugin' is not allowed to change device states. Currently, plugin 'control' has taken control. |
The control plugin has the controller role. Take the role in the constructor, or start the device with --control none. |
The device does not end after EXITING. |
The plugin has the controller role. Call ReleaseDeviceControl() in EXITING. |
| The options of the plugin do nothing. | The device ignores unknown options. With --severity debug, it logs detected unregistered option: …. Check that the plugin loads. |
An unsubscribe function throws std::out_of_range. |
UnsubscribeFromPropertyChange<T>() and UnsubscribeFromPropertyChangeAsString() need an existing subscription. Call them only after the subscription. |
Related pages
Section titled “Related pages”- Plugins and control: how plugins load, and the controller role.
- Control a device from code: a controller plugin step by step.
- custom-controller: a complete controller plugin.
- Configuration and properties: the properties that a plugin reads and sets.