example/plugin/testShape.cpp is the host program. It links Shape and dsold (and dl), and never includes Circle.hh / Square.hh / Ellipse.hh — it only knows the base.
add_executable( testShape testShape.cpp )
target_link_libraries( testShape dsold dl Shape )
Setup and the config demo
using namespace plugin;
using namespace plugin::example;
using namespace fedem::dso;
int main()
{
DSOLoader::prefix( "lib"s );
try { DSOLoader::load( "./libCircle.so"s ); } catch( std::exception& ) {}
try { DSOLoader::load( "./libEllipse.so"s ); } catch( std::exception& ) {}
try { DSOLoader::load( "./libSquare.so"s ); } catch( std::exception& ) {}
prefix("lib")— only matters for theloadAllpath used later in the interactive loop; harmless here.- Three explicit
loadcalls. Each runs the DSO'sREGISTER/REGISTER_WITH_CONFIG, so afterwardsShape's catalog holds"Circle","Ellipse","Square". A failed load is swallowed — the program still runs with whatever loaded.
auto circle = Shape::create( "Circle", Circle::Config{ 42 } );
if( circle ) { std::cout << "Circle (config r=42): "; circle->printOn( std::cout ); }
auto ellipse = Shape::create( "Ellipse", Ellipse::Config{ 10, 20 } );
if( ellipse ) { std::cout << "Ellipse (config a=10 b=20): "; ellipse->printOn( std::cout ); }
auto c2 = Shape::create( "Circle" ); // default ctor
if( c2 ) { c2->setSize( 7 ); std::cout << "Circle (default + setSize 7): "; c2->printOn( std::cout ); }
auto square = Shape::create( "Square" ); // REGISTER — no config
if( square ) { square->setSize( 15 ); std::cout << "Square (default + setSize 15): "; square->printOn( std::cout ); }
Output:
=== Config-based creation demo ===
Circle (config r=42): Circle r:42
Ellipse (config a=10 b=20): Ellipse a:10 b:20
Circle (default + setSize 7): Circle r:7
Square (default + setSize 15): Square a:15
Every create returns std::unique_ptr<Shape> — the driver checks it before use, because an unknown key (or a DSO that failed to load) gives nullptr.
The interactive loop
do {
auto addons = Plugin< Shape >::catalog().names( true ); // include aliases
std::cout << "Available addons:";
for( auto const& n : addons ) std::cout << ' ' << n;
std::cout << "\nshape/command : ";
std::string cmd; std::cin >> cmd;
if( !std::cin || cmd == "exit" || cmd == "quit" || cmd == "q" ) break;
if( cmd == "list" ) { for( auto const& s : objectList ) s->printOn( std::cout ); continue; }
// not in the catalog yet? try to load lib<cmd>.so on demand
if( std::find( addons.begin(), addons.end(), cmd ) == addons.end() )
{
auto file = DSOLoader::prefix() + cmd + ".so"s; // "lib" + cmd + ".so"
try { DSOLoader::load( "./"s + file ); }
catch( std::exception& ) {
try { DSOLoader::load( file ); } // then the search path
catch( std::exception& ) { std::cerr << "Could not load DSO for '" << cmd << "'\n"; continue; }
}
}
auto shape = Shape::create( cmd );
if( !shape ) { std::cout << '`' << cmd << "` could not be created!\n"; continue; }
unsigned int size; std::cout << "size : "; std::cin >> size;
shape->setSize( size );
std::cout << *shape << '\n'; // free operator<<
objectList.push_back( std::move( shape ) );
} while( true );
This is the pattern that makes the toolkit worth using: type Triangle and, if libTriangle.so is next to the binary, it is loaded, its REGISTER fires, catalog().names() now lists Triangle, and Shape::create("Triangle") works — with no code in the host that mentions triangles.
catalog().names(true) includes aliases; create accepts any key, alias or not.
Next: Signing an add-on.

