Why does create() return nullptr instead of throwing on an unknown key?

Because an unknown key is usually a missing plugin, not a programming error — the DSO failed to load, or was never deployed. That is a condition the host recovers from (fall back to a default, log, skip), so it is a return value. The genuine errors — wrong config type, config passed to a plugin that has none — do throw.

Is DSOLoader thread-safe?

Yes. Every operation takes a std::mutex. PluginCatalog uses a std::shared_mutex — concurrent create() calls run in parallel, insert / erase take the write lock. In practice registration happens during DSO load (single-threaded static init) and creation happens later, so contention is minimal.

Why RTLD_GLOBAL? Isn't that a namespace-pollution risk?

It is a deliberate trade-off. RTLD_GLOBAL is what lets a plugin resolve the host's CREATECATALOG symbol (so all registrations land in one catalog) and lets plugins share a common support library without re-linking. The cost is that two plugins exporting the same symbol name will collide. Keep plugin symbols in anonymous namespaces or marked hidden (DSOVisibility.hh / -fvisibility=hidden) and this does not bite.

Can I unload a plugin?

Not through the public API. Handles are dlclosed only at process exit. A plugin's registration is removed on unload (the PluginRegisterer destructor), so the machinery is correct if you dlclose a handle yourself — but you are then responsible for ensuring no instance from that DSO is still alive, including ones held in std::unique_ptr<Base>.

Does it work on Windows / macOS?

The loader is dlopen-based; macOS should work, Windows would need a LoadLibrary shim (not provided). The signer walks the ELF section table, so signing is Linux/ELF only today. Mach-O / PE support is a possible follow-up.

What is the relationship to FFS?

FFS (the Fedem Feature Server) is where this code grew up — it loads .ffs plugin modules with DSOLoader and gates them with plugin-sign. The dso-* tools moved out of the FFS tree into this package during the Conan migration so they travel with the library they belong to. FFS itself stays proprietary; plugin is MIT.

Do I need OpenSSL?

Only for the signing half. dsold + the plugin templates build with just the standard library and libdl. When OpenSSL is absent, CMake skips plugin-sign, the three tools, and the loadSigned / loadVerified overloads — nothing else changes.

How big is the runtime?

libdsold.so is a few tens of KB. The plugin templates are header-only and add nothing at runtime beyond the catalog (std::map keyed by std::string). plugin-sign links OpenSSL's libcrypto for Ed25519 and SHA-256.

Where is the Config type erased?

In Creator<Base>. REGISTER_WITH_CONFIG stores a std::function<Base*(std::any const&)> that does std::any_cast<Config const&> and calls new Class( cfg ). The std::any is constructed in Plugin::create(key, config) from the caller's concrete type, so the cast succeeds exactly when the types match.