PluginCore is a C++ library for building a fully plugin-based application: the host creates PluginCore::Core, and functionality is provided by dynamic libraries from the plugins directory.
Dev-workspace
sh <(curl -fsSL https://gitlab.bubki.zip/d3156/PluginCore/-/raw/main/tools/create_workspace.sh)Runtime-workspace
sh <(curl -fsSL https://gitlab.bubki.zip/d3156/PluginCore/-/raw/main/tools/runtime_install.sh)Answer the script prompts (workspace name, etc.). Process substitution <( ... ) lets bash execute the downloaded script without saving it as a file.
Go to the created workspace directory and run:
python3 ./tools/gen_plugin.pyAnswer the prompts about the plugin and model(s), then start developing.
The tools directory (under version control, you can periodically git pull it) contains utilities for working with the workspace.
addEasyBuildCommands.sh- Adds shorcat aliases in.bashrcfor CMake builds:- cmbr - configure and build the current directory as a
Releaseproject. - cmbd - configure and build the current directory as a
Debugproject. - cmbrc - clean, configure, and build the current directory as a
Releaseproject. - cmbdc - clean, configure, and build the current directory as a
Debugproject.
- cmbr - configure and build the current directory as a
build_all_release.sh- builds all projects in the workspace inReleasemode.build_all_debug.sh- builds all projects in the workspace inDebugmode.clean_build.sh- remove allbuildandbuild-debugdirectories in workspace.clangd_reconfigure.sh- generates thecompile_commands.jsonfile for the clangd language server.gen_plugins.py- Generates a new plugin and model through an interactive questionnaire.updateDepsList.sh- Clones all repositories required for this workspace if they are not already present; otherwise performs agit pull
.vscode/- a directory symlinked from the workspace root to use Visual Studio Code with this project.workspace.cmake- the base CMake configuration used by all plugins, libraries, and models in this workspace.create_workspace.sh- creates a new workspace with a specified name. Run this script from the directory where you want the workspace to be located. Usage: Run all scripts (except create_workspace.sh) from the root of the workspace, for example:
sh ./tools/clean_build.shRecommended setup:
# Install Debian packages:
sudo apt update
sudo apt install git default-jre graphviz clang clangd lldb cmake build-essential dotnet-runtime-10.0
# Install VS Code extensions via command line:
code --install-extension llvm-vs-code-extensions.vscode-clangd
code --install-extension vadimcn.vscode-lldb
code --install-extension eamodio.gitlens
code --install-extension go2sh.cmake-integration-vscode
code --install-extension jebbs.plantuml
code --install-extension josetr.cmake-language-support-vscode
code --install-extension ms-vscode.cmake-tools
code --install-extension twxs.cmake
# Install manually (if needed):
code --install-extension vscode-icons-team.vscode-iconsThe .vscode directory (symlinked from ./tools) contains pre-configured settings for project debugging.
The workspace also includes predefined VS Code tasks. Use Ctrl+Shift+P, select "Tasks: Run Task", and choose from the following:
build-all-debug- runs thebuild_all_debug.shscript.Reconfigure clangd- runsclangd_reconfigure.shand restarts the clangd server to apply the newcompile_commands.jsonfile.
When PluginCore::Core(argc, argv) is created, the core:
- Loads plugins from
./Plugins(or fromPLUGINS_DIRif the environment variable is set). - Calls
registerArgs()for each plugin, then parses command-line arguments, then callsregisterModels(). - After all models are registered, calls
postInit()for all models, and only then callspostInit()for all plugins.
By default, plugins are searched in ./Plugins (constant client_plugins_path).
The path can be overridden via the PLUGINS_DIR environment variable.
Shared library names must match:
lib<PluginName>.so(release)lib<PluginName>.Debug.so(when built withDEBUG)
A plugin implements the PluginCore::IPlugin interface.
Required:
void registerModels(ModelsStorage &storage)— register models and obtain dependencies via the models registry.
Optional:
void registerArgs(Args::Builder &bldr)— register command-line arguments (called beforeregisterModels).void postInit()— extra initialization afterpostInit()of all models.
Each plugin must export the following C-ABI functions with exact names:
extern "C" PluginCore::IPlugin *create_plugin();extern "C" void destroy_plugin(PluginCore::IPlugin *);
Core creates a plugin via create_plugin() and destroys it via destroy_plugin() (not via delete).
Communication between plugins is implemented via models (PluginCore::IModel) and their registry ModelsStorage.
A model must have a default constructor and perform all initialization in init().
Model destruction order is controlled by deleteOrder(): the smaller the value, the earlier the model is destroyed (negative values are supported).
To register/get a model you can use the macro:
models.registerModel<T>()It returns an existing model if it is already registered; otherwise it registers the provided one.
A host application typically just creates PluginCore::Core:
#include <PluginCore/Core>
#include <csignal>
#include <cerrno>
#include <cstring>
#include <unistd.h>
static volatile sig_atomic_t g_stop = 0;
static void on_term(int /*signum*/) {
g_stop = 1;
}
int main(int argc, char* argv[]) {
struct sigaction sa;
std::memset(&sa, 0, sizeof(sa));
sa.sa_handler = on_term;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGTERM, &sa, nullptr);
d3156::PluginCore::Core core(argc, argv);
while (!g_stop) pause();
return 0;
}All “real logic” lives in plugins: they can start their own threads and/or async tasks inside registerModels()/postInit().
You can implement the main thread differently—this is just an example.
If you want fully async logic, you can avoid creating a separate thread and run (for example) boost::io_context in a model’s postInit().
In that case the application will not exit after loading plugins, but shutdown signal handling will need to be implemented inside that model.
The idea is that the main thread should belong to the Core. When the OS requests a graceful shutdown via a signal, the Core should propagate a shutdown command to plugins. Plugins can then use any async model in their own threads (different libraries, their own event loops, etc.) as long as the shutdown signal is handled correctly.
Example implementation: ./PluginLoader
PluginCore is built as a static library PluginCore (CMake, C++20).
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
cpack --config build/CPackConfig.cmake -G DEBUse the script ./tools/gen_plugin.py or
-
Create a shared library libMyPlugin.so (or libMyPlugin.Debug.so for DEBUG).
-
Implement a class derived from PluginCore::IPlugin and at minimum registerModels(ModelsStorage&).
-
Export create_plugin() and destroy_plugin() with C ABI.
-
Put the .so into ./Plugins or set PLUGINS_DIR.
Minimal skeleton:
// MyPlugin.cpp
#include <PluginCore/IPlugin>
#include <PluginCore/IModel>
class MyPlugin final : public PluginCore::IPlugin {
public:
void registerModels(PluginCore::ModelsStorage& models) override {
// Register your models or obtain existing ones:
// auto* m = models.getModel<SomeModel>("SomeModel");
// models.registerModel(new MyModel());
}
};
extern "C" PluginCore::IPlugin* create_plugin() {
return new MyPlugin();
}
extern "C" void destroy_plugin(PluginCore::IPlugin* p) {
delete p;
}