一套基于 Qt 插件模型的模块化框架。每个动态库统一暴露五个生命周期接口:
Start / Initialize / Execute / SetCallback / Stop。其中Start创建业务对象,Initialize初始化业务对象,Execute执行主程序下发的任务,SetCallback注册主程序回调,Stop停止、销毁并立即卸载插件 。主程序由MainApp统一持有运行时容器,容器对外只暴露四个动作:start / initialize / execute / stop。本文以"远端传感器模块控制动态库"为例,给出框架骨架。
一、生命周期契约
接口语义与状态迁移:
| 接口 | 职责 | 终点状态 |
|---|---|---|
start() |
创建业务对象(轻量 new) |
Started |
initialize() |
初始化业务对象(线程、设备、配置) | Initialized |
execute(msg) |
处理一次业务请求 | Initialized |
setCallback(cb) |
注册主程序回调 | --- |
stop() |
停止运行 + 销毁业务对象 + 立即卸载插件 | Unloaded |
状态机:
Loaded ──start()──► Started ──initialize()──► Initialized ──► execute(msg) ──►
│ ↑ │
│ stop() ──────────
▼
Unloaded
对外调用链:
setEventSink → start → initialize → execute ... → stop(内含 unload)
stop 内部完成:停线程 → 删业务对象 → loader->unload() → delete loader → 从容器移除。之后该模块在容器里彻底消失,若需复用必须重新 start。
二、设计目标
-
依赖单向无环 :
MainApp → 公共接口 ← 动态库,插件不反向依赖主程序。 -
接口层无状态:只放纯虚类、回调类型、导出宏,可安全复制多份。
-
创建与初始化分离 :
start只建对象,initialize做重活,失败点清晰可追踪。 -
职责分层:壳做接口适配,控制器管生命周期,业务对象专注干活。
三、目录结构
project/
├── CMakeLists.txt
├── PublicFrame/
│ ├── CMakeLists.txt
│ └── include/
│ ├── ModuleExport.h
│ ├── ModuleCallback.h
│ └── ModuleInterface.h
├── App/
│ ├── CMakeLists.txt
│ ├── main.cpp
│ ├── MainApp.h / .cpp
│ └── ModuleRuntime.h / .cpp
└── RemoteSensorModule/
├── CMakeLists.txt
├── RemoteSensorModule.h / .cpp (壳)
├── SensorController.h / .cpp (主线程控制器)
└── SensorCollector.h / .cpp (工作线程采集器)
四、公共接口层 PublicFrame
PublicFrame 是 INTERFACE 库(header-only)。接口类不继承 QObject,无 moc 单元、无状态、无符号,可被所有目标链接而不产生副本冲突。
// ModuleExport.h
#pragma once
#include <QtCore/qglobal.h>
#if defined(MODULE_LIBRARY)
# define MODULE_EXPORT Q_DECL_EXPORT
#else
# define MODULE_EXPORT Q_DECL_IMPORT
#endif
// ModuleCallback.h
#pragma once
#include <QString>
#include <QByteArray>
using ModuleCallback = void (*)(const QString& source, const QByteArray& event);
// ModuleInterface.h
#pragma once
#include <QString>
#include "ModuleCallback.h"
/**
* 模块接口
* 调用顺序:setCallback → start → initialize → execute ... → stop
* start 创建业务对象
* initialize 初始化业务对象
* execute 执行请求
* stop 停止 + 销毁 + 卸载(终态)
* 回调可能在任意线程触发,接收方必须自行派发。
*/
class Module
{
public:
virtual ~Module() = default;
virtual void start() = 0;
virtual bool initialize() = 0;
virtual void execute(QString& msg) = 0;
virtual void setCallback(ModuleCallback cb) = 0;
virtual void stop() = 0;
};
#define MODULE_IID "com.company.PluginModule"
Q_DECLARE_INTERFACE(Module, MODULE_IID)
cmake:
# PublicFrame/CMakeLists.txt
add_library(PublicFrame INTERFACE)
target_include_directories(PublicFrame INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(PublicFrame INTERFACE Qt5::Core)
五、运行时容器 ModuleRuntime
容器职责收窄为"加载 → 建对象 → 初始化 → 执行 → 停止卸载"这条单链。
内部用一个 Entry 结构承载每个模块的 loader、impl、就绪标志。
// ModuleRuntime.h
#pragma once
#include <QObject>
#include <QHash>
#include <QPluginLoader>
#include "ModuleInterface.h"
class ModuleRuntime : public QObject
{
Q_OBJECT
public:
explicit ModuleRuntime(QObject* parent = nullptr);
~ModuleRuntime() override;
void setEventSink(ModuleCallback cb) { m_sink = cb; }
bool start(const QString& id); // 加载 + 创建业务对象
bool initialize(const QString& id); // 初始化业务对象
bool execute(const QString& id, QString& msg);
void stop(const QString& id); // 停止 + 销毁 + 卸载
bool isReady(const QString& id) const;
private:
struct Entry {
QPluginLoader* loader = nullptr;
Module* impl = nullptr;
bool ready = false; // 已 initialize
};
bool attach(const QString& id); // 内部:加载插件 + setCallback
QString resolvePath(const QString& id) const;
QHash<QString, Entry> m_entries;
ModuleCallback m_sink = nullptr;
};
// ModuleRuntime.cpp
#include "ModuleRuntime.h"
#include <QCoreApplication>
#include <QDir>
#include <QDebug>
ModuleRuntime::ModuleRuntime(QObject* parent) : QObject(parent) {}
ModuleRuntime::~ModuleRuntime()
{
// 兜底:所有仍存活的模块一律走 stop(含卸载)
const auto ids = m_entries.keys();
for (const QString& id : ids) stop(id);
}
QString ModuleRuntime::resolvePath(const QString& id) const
{
const QString dir = QCoreApplication::applicationDirPath();
#ifdef Q_OS_WIN
return QDir(dir).filePath(id + ".dll");
#elif defined(Q_OS_MAC)
return QDir(dir).filePath("lib" + id + ".dylib");
#else
return QDir(dir).filePath("lib" + id + ".so");
#endif
}
bool ModuleRuntime::attach(const QString& id)
{
if (m_entries.contains(id)) return true;
auto* loader = new QPluginLoader(resolvePath(id), this);
if (!loader->load()) {
qWarning() << "attach failed:" << id << loader->errorString();
delete loader;
return false;
}
QObject* obj = loader->instance();
auto* impl = obj ? qobject_cast<Module*>(obj) : nullptr;
if (!impl) {
loader->unload();
delete loader;
return false;
}
Entry e;
e.loader = loader;
e.impl = impl;
if (m_sink) impl->setCallback(m_sink); // 回调早于 start 下发
m_entries.insert(id, e);
return true;
}
bool ModuleRuntime::start(const QString& id)
{
if (m_entries.contains(id)) return true;
if (!attach(id)) return false;
m_entries[id].impl->start(); // 创建业务对象
return true;
}
bool ModuleRuntime::initialize(const QString& id)
{
auto it = m_entries.find(id);
if (it == m_entries.end()) return false;
if (it->ready) return true;
if (!it->impl->initialize()) {
qWarning() << "initialize failed:" << id;
return false;
}
it->ready = true;
return true;
}
bool ModuleRuntime::execute(const QString& id, QString& msg)
{
auto it = m_entries.find(id);
if (it == m_entries.end() || !it->ready) return false;
it->impl->execute(msg);
return true;
}
void ModuleRuntime::stop(const QString& id)
{
auto it = m_entries.find(id);
if (it == m_entries.end()) return;
it->impl->stop(); // 停运行 + 删业务对象
it->loader->unload(); // 卸载插件
delete it->loader;
m_entries.erase(it); // 容器移除,终态
}
bool ModuleRuntime::isReady(const QString& id) const
{
auto it = m_entries.constFind(id);
return it != m_entries.constEnd() && it->ready;
}
六、主程序 MainApp
MainApp 是业务入口、运行时容器持有者、回调接收者三合一。main.cpp 只负责创建 MainApp ,其余全部由 MainApp 内部完成。
// MainApp.h
#pragma once
#include <QObject>
#include <QByteArray>
#include <QString>
class ModuleRuntime;
class MainApp : public QObject
{
Q_OBJECT
public:
explicit MainApp(QObject* parent = nullptr);
~MainApp() override;
bool boot(const QString& moduleId); // start + initialize
void shutdown(const QString& moduleId); // stop(含卸载)
bool command(const QString& moduleId, const QString& cmd);
static void onModuleEvent(const QString& source, const QByteArray& event);
public slots:
void handleEvent(const QString& source, const QByteArray& event);
private:
ModuleRuntime* m_runtime = nullptr;
static MainApp* s_self;
};
// MainApp.cpp
#include "MainApp.h"
#include "ModuleRuntime.h"
#include <QThread>
#include <QMetaObject>
#include <QDebug>
MainApp* MainApp::s_self = nullptr;
MainApp::MainApp(QObject* parent) : QObject(parent)
{
s_self = this;
m_runtime = new ModuleRuntime(this);
m_runtime->setEventSink(&MainApp::onModuleEvent);
}
MainApp::~MainApp()
{
s_self = nullptr;
// m_runtime 是 this 的子对象,随析构自动清理(其析构内含 stop 兜底)
}
bool MainApp::boot(const QString& moduleId)
{
if (!m_runtime->start(moduleId)) return false;
if (!m_runtime->initialize(moduleId)) return false;
return true;
}
void MainApp::shutdown(const QString& moduleId)
{
m_runtime->stop(moduleId); // 停止 + 销毁 + 卸载
}
bool MainApp::command(const QString& moduleId, const QString& cmd)
{
QString m = cmd;
return m_runtime->execute(moduleId, m);
}
void MainApp::onModuleEvent(const QString& source, const QByteArray& event)
{
MainApp* self = s_self;
if (!self) return;
if (QThread::currentThread() == self->thread())
self->handleEvent(source, event);
else
QMetaObject::invokeMethod(self, "handleEvent", Qt::QueuedConnection,
Q_ARG(QString, source), Q_ARG(QByteArray, event));
}
void MainApp::handleEvent(const QString& source, const QByteArray& event)
{
// 主线程:按 source 路由到对应业务处理器
qInfo() << "[event]" << source << event;
}
// main.cpp ------ 只做创建与启动
#include <QCoreApplication>
#include "MainApp.h"
int main(int argc, char* argv[])
{
QCoreApplication app(argc, argv);
MainApp mainApp;
if (!mainApp.boot("RemoteSensorModule")) {
qFatal("boot failed");
return -1;
}
// ... 运行期业务:mainApp.command("RemoteSensorModule", "read-once");
mainApp.shutdown("RemoteSensorModule");
return 0;
}
七、动态库:远端传感器模块
壳 → 控制器 → 采集器,三层职责逐层收窄:
RemoteSensorModule(壳) 接口适配;start 建控制器,stop 停 + 删控制器
│ 持有
▼
SensorController(主线程) 管线程、管采集器;init 起线程,halt 停 + 删
│ 持有
▼
SensorCollector(工作线程) 专注采集与回调
7.1 壳
// RemoteSensorModule.h
#pragma once
#include <QObject>
#include "ModuleInterface.h"
class SensorController;
class RemoteSensorModule : public QObject, public Module
{
Q_OBJECT
Q_PLUGIN_METADATA(IID MODULE_IID)
Q_INTERFACES(Module)
public:
explicit RemoteSensorModule(QObject* parent = nullptr);
~RemoteSensorModule() override;
void start() override; // 创建控制器
bool initialize() override; // 初始化控制器
void execute(QString& msg) override;
void setCallback(ModuleCallback cb) override;
void stop() override; // 停止并销毁控制器
private:
SensorController* m_ctl = nullptr;
ModuleCallback m_callback = nullptr;
};
// RemoteSensorModule.cpp(节选)
void RemoteSensorModule::start()
{
if (m_ctl) return;
m_ctl = new SensorController();
if (m_callback) m_ctl->setCallback(m_callback);
}
bool RemoteSensorModule::initialize()
{
return m_ctl ? m_ctl->init() : false;
}
void RemoteSensorModule::execute(QString& msg)
{
if (m_ctl) m_ctl->handleRequest(msg);
}
void RemoteSensorModule::setCallback(ModuleCallback cb)
{
m_callback = cb;
if (m_ctl) m_ctl->setCallback(cb);
}
void RemoteSensorModule::stop()
{
if (!m_ctl) return;
m_ctl->halt(); // 停线程、停采集、删采集器
delete m_ctl;
m_ctl = nullptr;
}
RemoteSensorModule::~RemoteSensorModule() { stop(); }
回调双保险:setCallback 早到则 start 时传给控制器,晚到则 setCallback 时转发给已存在的控制器。
7.2 控制器
// SensorController.h
#pragma once
#include <QObject>
#include "ModuleCallback.h"
class QThread;
class SensorCollector;
class SensorController : public QObject
{
Q_OBJECT
public:
explicit SensorController(QObject* parent = nullptr);
~SensorController() override;
bool init(); // 建线程、建采集器、启动采集
void halt(); // 停采集、停线程、删采集器
void handleRequest(const QString& msg);
void setCallback(ModuleCallback cb);
private:
QThread* m_thread = nullptr;
SensorCollector* m_coll = nullptr;
ModuleCallback m_callback = nullptr;
};
// SensorController.cpp(节选)
bool SensorController::init()
{
if (m_coll) return true;
m_thread = new QThread();
m_coll = new SensorCollector();
m_coll->moveToThread(m_thread);
if (m_callback) m_coll->setCallback(m_callback);
m_thread->start();
QMetaObject::invokeMethod(m_coll, "beginCollect", Qt::QueuedConnection);
return true;
}
void SensorController::halt()
{
if (!m_coll) return;
// 阻塞等待采集器停干净,保证 halt 返回后无在途回调
QMetaObject::invokeMethod(m_coll, "endCollect", Qt::BlockingQueuedConnection);
m_thread->quit();
m_thread->wait();
delete m_coll; m_coll = nullptr;
delete m_thread; m_thread = nullptr;
}
7.3 采集器
// SensorCollector.h
#pragma once
#include <QObject>
#include <QTimer>
#include "ModuleCallback.h"
class SensorCollector : public QObject
{
Q_OBJECT
public:
explicit SensorCollector(QObject* parent = nullptr);
void setCallback(ModuleCallback cb) { m_callback = cb; }
public slots:
void beginCollect();
void endCollect();
void handleRequest(const QString& msg);
private slots:
void onSample();
private:
QTimer* m_timer = nullptr;
ModuleCallback m_callback = nullptr;
quint64 m_seq = 0;
};
// SensorCollector.cpp(节选)
void SensorCollector::beginCollect()
{
if (!m_timer->isActive()) m_timer->start(1000);
}
void SensorCollector::endCollect() { m_timer->stop(); }
void SensorCollector::onSample()
{
QByteArray payload = QByteArray::number(++m_seq) + "@" +
QByteArray::number(QDateTime::currentMSecsSinceEpoch());
if (m_callback) m_callback("RemoteSensorModule", payload);
}
八、CMake
# 根
cmake_minimum_required(VERSION 3.16)
project(QtModuleDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
find_package(Qt5 REQUIRED COMPONENTS Core)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
add_subdirectory(PublicFrame)
add_subdirectory(RemoteSensorModule)
add_subdirectory(App)
cmake
# RemoteSensorModule
add_library(RemoteSensorModule MODULE
RemoteSensorModule.cpp SensorController.cpp SensorCollector.cpp)
target_compile_definitions(RemoteSensorModule PRIVATE MODULE_LIBRARY)
target_link_libraries(RemoteSensorModule PRIVATE PublicFrame Qt5::Core)
cmake
# App
add_executable(App main.cpp MainApp.cpp ModuleRuntime.cpp)
target_link_libraries(App PRIVATE PublicFrame Qt5::Core)
add_dependencies(App RemoteSensorModule)
resolvePath 按平台拼 lib<id>.so / <id>.dll / lib<id>.dylib,target 名即 id,主程序调用统一传 "RemoteSensorModule"。
九、完整时序
MainApp ModuleRuntime RemoteSensorModule.so
│ │ │
│ 构造 │ │
├─ new ModuleRuntime ─────►│ │
├─ setEventSink(cb) ──────►│ 保存回调 │
│ │ │
├─ boot("RemoteSensorModule") │
│ ├─ start() ────────────►│ │
│ │ ├─ attach: load + setCb ─►│ 壳保存回调
│ │ ├─ impl->start() ────────►│ new Controller
│ ├─ initialize() ───────►│ │
│ │ ├─ impl->initialize() ───►│ Controller.init()
│ │ │ │ ├ new Thread
│ │ │ │ ├ new Collector + moveToThread
│ │ │ │ ├ thread.start()
│ │ │ │ └ invokeMethod(beginCollect)
│ │ │
│ │ │ [工作线程] 周期 onSample
│ │ │ └ callback("RemoteSensorModule", data)
│◄──── 回调(工作线程) ──────┼──────────────────────────┤
│ invokeMethod → 主线程 handleEvent │
│ │ │
├─ command(...) ──────────►│ │
│ ├─ impl->execute(msg) ───►│ Controller.handleRequest
│ │ │
├─ shutdown(...) │ │
│ └─ stop() ─────────────►│ │
│ ├─ impl->stop() ──────────►│ Controller.halt()
│ │ │ ├ BlockingQueued(endCollect)
│ │ │ ├ thread.quit/wait
│ │ │ └ delete Collector / Thread
│ │ │ 壳 delete Controller
│ ├─ loader->unload() ──────►│ 触发壳析构(stop 幂等)
│ ├─ delete loader │
│ └─ entries.erase(id) │ 容器移除,终态
十、关键设计决策
1. Start 创建、Initialize 初始化
start 只做 new,轻量、失败点少;initialize 做重活(线程、设备、配置)。两个动作拆开,能精确定位是"对象建不出来"还是"建出来了但起不来",execute 也只需判断 Entry::ready 即可。
2. Stop 是唯一终态入口
取消 shutdown,stop 内部完成:停线程 → 删业务对象 → loader->unload() → delete loader → 容器移除。没有任何"已停止未卸载"的中间状态 。调用方只需记住:start 进来的东西,stop 一次性全部清掉。也杜绝了"漏调 shutdown 导致 unload 失败"的隐患。
3. ModuleRuntime 归属 MainApp
main.cpp 只做 QCoreApplication 和 MainApp 的创建。MainApp 在构造时创建 ModuleRuntime 并注入回调,在析构时随父子关系自动清理(ModuleRuntime::~ModuleRuntime 兜底 stop 所有存活模块)。业务入口与容器彻底归拢在 MainApp 内部,main 只剩骨架。
4. 接口不继承 QObject
接口无 moc、无状态、无符号,PublicFrame 作为 INTERFACE 库可被所有目标链接而不冲突。Q_DECLARE_INTERFACE 提供类型安全的 qobject_cast。
5. 回调双时机兜底
setEventSink 早于 start 时,attach 阶段就下发;晚于 start 时,壳的 setCallback 把回调转发给已存在的控制器。两种顺序都工作。
6. 回调线程不假设
采集器在工作线程直接触发回调,MainApp::onModuleEvent 判断当前线程,跨线程走 QueuedConnection 派发到主线程。这是框架里唯一需要纪律的地方:接收方必须自行派发。
7. 壳 / 控制器 / 采集器分层
壳做接口适配,控制器住主线程管线程与对象生命周期,采集器住工作线程做业务。采集器可脱离插件体系独立单测,可替换为不同厂商驱动。层间只通过明确接口交互,耦合度低。
十一、总结
框架的三条边界:
-
依赖边界 :
MainApp → 公共接口 ← 动态库,单向无环;公共接口无状态。 -
职责边界:Runtime 管编排、壳管适配、控制器管调度、采集器管业务;每层只做一件事。
-
线程边界 :跨线程通信一律
invokeMethod/ 信号槽,回调接收方统一派发到主线程。
新增模块:
-
在根目录下新建模块目录,实现
Module接口壳(start建对象、initialize初始化、stop销毁); -
按需拆分控制器与业务对象;
-
CMake 里加一个
MODULE库;
start 建对象、initialize 初始化、stop 一步到底------生命周期只有一条正向路径,容器与主程序各就各位,这套结构足以作为项目的长期底座。