fedem::dso::DSOLoader is the loader. Every function is static, and the class cannot be instantiated — it is a namespace with a mutex.

#include <dso/DSOLoader.hh>
using fedem::dso::DSOLoader;

Loading one file

DSOLoader::load( "./libCircle.so" );
DSOLoader::load( "libCircle.so", { "/opt/app/plugins", "/usr/lib/app" } );

The single-argument form passes the string straight to dlopen, so the platform search rules apply (RPATH, LD_LIBRARY_PATH, absolute/relative path). The two-argument form tries each directory in the list until one loads.

dlopen is called with RTLD_GLOBAL | RTLD_LAZY:

  • RTLD_GLOBAL — the DSO's symbols join the global scope, so a second plugin can resolve against the first, and the host's CREATECATALOG symbol is visible to the plugin. This is what makes the shared-catalog design work.
  • RTLD_LAZY — relocations are resolved on first use.

A successful load runs the DSO's static initialisers — that is when REGISTER fires and the plugin's keys appear in the catalog.

Loading many at once

DSOLoader::loadAll( ".so", "/opt/app/plugins" );   // every *.so in that dir
DSOLoader::loadAll( ".so" );                        // every *.so in the cwd
DSOLoader::loadAll( ".so", { "/opt/app/plugins", "/usr/lib/app/plugins" } );

loadAll scans the directory (non-recursively), matches regular files and symlinks whose name ends in the extension — and, if a prefix is set, begins with it — and loads each. Files that fail to load are collected; if any failed, loadAll throws FileNotFound naming them, after loading the rest.

DSOLoader::loadAllByEnvironment( ".so", "APP_PLUGIN_PATH" );  // each dir on $APP_PLUGIN_PATH
DSOLoader::loadAllByEnvironment( ".so" );                     // $LD_LIBRARY_PATH (or the
                                                             //   platform equivalent)

loadAllByEnvironment splits the named environment variable on : (; on Cygwin) and runs loadAll for each directory. The no-argument overload uses LD_LIBRARY_PATH on Linux, LIBPATH on AIX, SHLIB on HP-UX, PATH on Cygwin. An unset variable is a no-op.

The filename prefix

DSOLoader::prefix( "lib" );          // set
std::string p = DSOLoader::prefix(); // get  ->  "lib"

When set, loadAll and loadAllByEnvironment only consider files that start with the prefix. It does not affect the single-file load(). Set it to "lib" to skip stray non-plugin .sos in a shared directory, or to a project-specific tag ("app-plugin-").

Handle lifetime

Every loaded handle is stored in a process-wide set and dlclosed only at program exit (the set's destructor). There is deliberately no unload() in the public API: unloading a DSO whose types are still live — a unique_ptr<Shape> holding a Circle from libCircle.so — is a use-after-unload waiting to happen. If you need it, you own the dlclose and the ordering.

Loading the same file twice is cheap and harmless — dlopen reference-counts, and the second call returns the same handle.

Error handling

#include <dso/DSOExceptions.hh>

try {
    DSOLoader::load( "libCircle.so", searchDirs );
}
catch( fedem::exception::FileNotFound const& e ) {
    std::clog << "plugin missing: " << e.missingFilename()
              << " (" << e.reason() << ")\n";
}

FileNotFound carries the name it tried and the dlerror() string. It is thrown when no directory in the list yields a loadable file, and (as above) by loadAll when some files failed. DSOLoader never calls std::abort or exit on a load failure — a missing or broken plugin is a recoverable condition.

Next: Signing & Trust.