SeniorDesignOccasionalNot answered yet
Design a plugin system for a C++ host application: plugins ship as separate shared libraries (.so/.dll), are discovered and loaded at runtime rather than linked at build time, and a host registry keeps track of the loaded ones. The hard constraint is binary compatibility — a plugin built with a different compiler or compiler version than the host must still load and work correctly, and the host must be able to create and destroy plugin objects across that boundary without relying on C++ name mangling or fragile object layouts. Describe the architecture: how the host and plugin agree on a stable contract, how a plugin is loaded and its objects created and destroyed, and what must never cross the boundary.
A C++ plugin system uses a stable C ABI factory (extern C create_plugin), an abstract IPlugin base, dynamic loading via dlopen/LoadLibrary, and a host registry. The IPlugin header must stay binary-stable.
- ✗Exporting C++ classes directly without a C factory function — mangled names differ between compilers and even compiler versions; always use extern "C" at the boundary
- ✗Not unloading plugins in reverse order — if plugin B depends on plugin A and A is unloaded first, B's vtable points to destroyed code
- ✗Sharing STL containers across the plugin boundary —
std::string/std::vectorlayouts may differ across runtimes or compiler flags; use C types or pointers at the boundary
- →How does version negotiation work between host and plugin when the IPlugin interface evolves?
- →What is COM (Component Object Model) and how does it solve C++ ABI issues for plugins on Windows?
Contents
Plugin System Architecture
Plugin interface (plugin_api.h — stable header)
#pragma once
#include <cstdint>
class IPlugin {
public:
virtual ~IPlugin() = default;
virtual const char* name() const noexcept = 0;
virtual void execute(const char* input, char* output, uint32_t out_size) = 0;
};
extern "C" {
using CreatePluginFn = IPlugin* (*)();
using DestroyPluginFn = void (*)(IPlugin*);
}
Plugin implementation (separate .so/.dll)
#include "plugin_api.h"
#include <cstring>
class MyPlugin : public IPlugin {
public:
const char* name() const noexcept override { return "MyPlugin"; }
void execute(const char* in, char* out, uint32_t sz) override {
std::strncpy(out, in, sz - 1);
out[sz - 1] = '\0';
}
};
extern "C" {
IPlugin* create_plugin() { return new MyPlugin(); }
void destroy_plugin(IPlugin* p){ delete p; }
}
Host-side loader
#include "plugin_api.h"
#include <dlfcn.h>
#include <memory>
#include <stdexcept>
class PluginHandle {
public:
explicit PluginHandle(const std::string& path) {
lib_ = ::dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!lib_) throw std::runtime_error(::dlerror());
auto create = reinterpret_cast<CreatePluginFn>(::dlsym(lib_, "create_plugin"));
auto destroy = reinterpret_cast<DestroyPluginFn>(::dlsym(lib_, "destroy_plugin"));
if (!create || !destroy) throw std::runtime_error("Missing factory functions");
plugin_.reset(create(), [destroy](IPlugin* p){ destroy(p); });
}
~PluginHandle() {
plugin_.reset(); // destroy object first...
if (lib_) ::dlclose(lib_); // ...then unload the library
}
IPlugin* get() const { return plugin_.get(); }
private:
void* lib_ = nullptr;
std::unique_ptr<IPlugin, DestroyPluginFn> plugin_{nullptr, nullptr};
};Contents