This page walks the whole model once: a host that defines an extension point, a plugin that fills it, compiled to a separate shared object, and the host loading it at runtime and creating the type by name.
The code here is a trimmed version of example/plugin/ in the source tree — see the Examples for the full annotated version.
1. The host defines a base class
The extension point is an abstract base that derives from plugin::Plugin<Base> (CRTP — it passes itself as the template argument):
// Shape.hh — shipped by the host, included by both host and plugins
#pragma once
#include <ostream>
#include <plugin/Plugin.hh>
class Shape : public plugin::Plugin<Shape>
{
public:
virtual void setSize( unsigned int ) = 0;
virtual void printOn( std::ostream& ) const = 0;
};
Plugin<Shape> gives Shape the static create() and load() functions and a per-Base catalog(). That catalog needs exactly one definition, in a .cpp the host links:
// Shape.cpp — host only
#include "Shape.hh"
#include <plugin/PluginCatalog.hh>
CREATECATALOG( Shape );
2. A plugin registers itself
A plugin is a normal class deriving from Shape, plus one macro:
// Circle.cpp — compiled into libCircle.so, NOT linked into the host
#include "Shape.hh"
#include <plugin/PluginRegister.hh>
class Circle : public Shape
{
unsigned int r = 0;
public:
void setSize( unsigned int v ) override { r = v; }
void printOn( std::ostream& os ) const override { os << "Circle r:" << r << '\n'; }
};
REGISTER( Circle, Shape ); // "Circle" -> factory, added to Shape's catalog
REGISTER creates a file-scope object whose constructor inserts a Circle factory into Shape::catalog() under the key "Circle", and whose destructor removes it. That runs when libCircle.so is loaded and unloaded.
3. Build the plugin as a shared object
add_library( Circle MODULE Circle.cpp )
target_link_libraries( Circle PRIVATE plugin ) # header-only; brings in Shape.hh's deps
set_target_properties( Circle PROPERTIES PREFIX "lib" )
MODULE is the right CMake library type for a plugin — it is dlopen-only, never linked. The result is libCircle.so.
4. The host loads and creates
#include <dso/DSOLoader.hh>
#include "Shape.hh"
int main()
{
fedem::dso::DSOLoader::load( "./libCircle.so" ); // REGISTER fires here
auto shape = Shape::create( "Circle" ); // std::unique_ptr<Shape>
if( !shape )
return 1; // unknown key -> nullptr
shape->setSize( 7 );
shape->printOn( std::cout ); // Circle r:7
}
The host links dsold and plugin, and Shape.cpp. It never sees Circle.cpp or Circle.hh.
5. Pass a config
If the plugin has a Config struct, register with REGISTER_WITH_CONFIG and the host can hand one over — type-checked through std::any:
// in Circle.cpp
struct Circle::Config { unsigned int r = 0; };
Circle::Circle( Config const& c ) : r( c.r ) {}
REGISTER_WITH_CONFIG( Circle, Shape, Circle::Config );
// in the host
auto shape = Shape::create( "Circle", Circle::Config{ 42 } ); // Circle r:42
A wrong config type throws std::bad_any_cast; using the config overload on a plugin that registered without one throws std::runtime_error.
Where to go next
- Defining a Plugin Hierarchy — the base class, the catalog, and the one-catalog-per-
Baserule in detail. - Writing a Plugin — every registration macro, aliases, and when a factory returns
nullptrversus throws. - Loading Shared Objects —
load,loadAll,loadAllByEnvironment, the filename prefix, and error handling. - Signing & Trust — sign the add-on and refuse anything a trusted key did not vouch for.

