Qt QPluginLoader Architecture Migration
Table of Contents
Problem
The initial plugin extraction (Steps 3–8 of the Qt plugin refactor) produced
six STATIC domain library plugins wired by direct std::unique_ptr<XxxPlugin>
members in MainWindow. This is Option 3 from the architecture analysis
(explicit factory / registry) and does NOT achieve the isolation goal:
- Host must be relinked for every plugin change.
MainWindowhas compile-time dependencies on all domain headers.- Each plugin class duplicated identical signals and
connect_controller_signals.
Solution
Implement Option 1 (QPluginLoader + SHARED libraries) from the analysis
document.
Implementation (completed 2026-04-08)
Phase 1 — ores.qt.api framework changes
ores.qt.api was already a SHARED library (add_library(SHARED ...) with
ORES_QT_API_LIBRARY compile definition and BOOST_SYMBOL_EXPORT macros).
Changes made:
IPlugin.hpp— addedQ_DECLARE_INTERFACE(ores::qt::IPlugin, "ores.qt.IPlugin/1.0")and#include <QtPlugin>. This registers the interface IID used byQPluginLoaderfor ABI version checking.- New
PluginBaseclass (include/ores.qt/PluginBase.hpp+src/PluginBase.cpp). Inheritspublic QObject, public IPluginwithQ_OBJECT. Provides:- Signals:
statusMessage(const QString&),windowCreated(DetachableMdiSubWindow*),windowDestroyed(DetachableMdiSubWindow*). - Protected method:
connectControllerSignals(EntityController* ctrl)— wires the fourEntityControllersignals using modern functor syntax, eliminating the 6× per-plugin duplication.
- Signals:
PluginRegistryredesign (include/ores.qt/PluginRegistry.hpp+src/PluginRegistry.cpp).- Removed
register_plugin(std::unique_ptr<IPlugin>). - Added
load_from_directory(const QString& plugin_dir)— scans directory for shared libraries, loads eachIPluginviaQPluginLoader, sorts byload_order(). - Holds
QVector<QPluginLoader*> loaders_to keep plugins alive for the process lifetime.
- Removed
Phase 2 — Six domain plugins: STATIC → SHARED
For each of ores.qt.admin, ores.qt.compute, ores.qt.refdata,
ores.qt.party, ores.qt.mktdata, ores.qt.trading:
CMakeLists.txt
add_library(STATIC ...)→add_library(SHARED ...)- Added
LIBRARY_OUTPUT_DIRECTORYpointing to${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/pluginsso plugin.sofiles land inpublish/lib/plugins/during development builds. - Added
INSTALL_RPATH "$ORIGIN/.."(plugins/ → lib/ whereores.qt.api.solives). - Changed install target to
LIBRARY DESTINATION lib/plugins.
Plugin header
- Base changed from
public QObject, public IPlugin→public PluginBase. - Added
Q_PLUGIN_METADATA(IID "ores.qt.IPlugin/1.0")andQ_INTERFACES(ores::qt::IPlugin). - Removed per-plugin
signals:block (status_message,window_created,window_destroyed). - Removed per-plugin private forwarding slots and
connect_controller_signalsdeclaration.
Plugin source
- Constructor changed from
: QObject(parent)to: PluginBase(parent). - Removed
void XxxPlugin::connect_controller_signals(QObject*)implementation. - Replaced all
connect_controller_signals(ctrl)calls withconnectControllerSignals(ctrl). - Replaced any remaining
emit status_message(msg)/&XxxPlugin::status_messagewithemit statusMessage(msg)/&PluginBase::statusMessage.
Phase 3 — MainWindow decoupled from domain plugins
MainWindow.hpp: removed six typed plugin forward declarations and
std::unique_ptr<XxxPlugin> member variables.
MainWindow.cpp:
- Removed six plugin
#includedirectives; added#include "ores.qt/PluginBase.hpp"and#include "ores.qt/PluginRegistry.hpp". - Constructor: replaced 18 individual signal connects (3 per plugin × 6) with a
single loop over
PluginRegistry::instance().plugins(). onLoginSuccess: replaced six expliciton_login()+ sixcreate_menus()calls with a single loop.performDisconnectCleanup: replaced six expliciton_logout()calls with a reverse loop (last-loaded plugin logs out first).
Phase 4 — Plugin discovery in main.cpp
Added PluginRegistry::instance().load_from_directory(plugin_dir) before
MainWindow construction, where plugin_dir = applicationDirPath() + "/../lib/plugins".
At runtime (development build): publish/bin/../lib/plugins = publish/lib/plugins/.
Phase 5 — ores.qt CMakeLists cleanup
Removed ores.qt.admin.lib, ores.qt.compute.lib, ores.qt.refdata.lib,
ores.qt.party.lib, ores.qt.mktdata.lib, ores.qt.trading.lib from
ores.qt.lib link dependencies. These are now loaded at runtime by
QPluginLoader — no compile-time link required.
Known issues / future work
- Inter-plugin compile-time dependencies:
ores.qt.computelinksores.qt.admin(SessionHistoryDialog),ores.qt.mktdatalinksores.qt.refdata, andores.qt.tradinglinksores.qt.partyfor types not yet moved toores.qt.api. These should be resolved by moving shared types toores.qt.apiin a follow-up. - OrgExplorer null BusinessUnitController:
TradingPluginpassesnullptrforbusinessUnitControllertoOrgExplorerMdiWindowbecause this cross-plugin reference requires a different approach (e.g., a service interface on IPlugin). - ores.qt.app rename: the architecture analysis proposes renaming the host
static library to
ores.qt.app. Not done yet.