Qt:动态库模块框架设计

一套基于 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。


二、设计目标

  1. 依赖单向无环 :MainApp → 公共接口 ← 动态库,插件不反向依赖主程序。

  2. 接口层无状态:只放纯虚类、回调类型、导出宏,可安全复制多份。

  3. 创建与初始化分离 :start 只建对象,initialize 做重活,失败点清晰可追踪。

  4. 职责分层:壳做接口适配,控制器管生命周期,业务对象专注干活。


三、目录结构

复制代码
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 / 信号槽,回调接收方统一派发到主线程。

新增模块:

  1. 在根目录下新建模块目录,实现 Module 接口壳(start 建对象、initialize 初始化、stop 销毁);

  2. 按需拆分控制器与业务对象;

  3. CMake 里加一个 MODULE 库;

start 建对象、initialize 初始化、stop 一步到底------生命周期只有一条正向路径,容器与主程序各就各位,这套结构足以作为项目的长期底座。

相关推荐
quantdash_cc1 小时前
如何从多只股票的历史行情构建收益率矩阵?Python 与 QuantDash 实战教程
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
wuminyu1 小时前
JVM规范学习总结-JVM规范基本概念
java·linux·c语言·jvm·c++
Demon--hx2 小时前
jsoncpp 核心概念梳理:命名空间、Value 容器与格式化输出
c++
不是万能的豆学长5 小时前
filetidy 文件整理助手:一键归类 + 规范重命名(Python 零依赖单文件工具)
大数据·开发语言·python·技术美术
懂点Tech9 小时前
ctfshow MISC入门图片篇(信息附加):misc41解题思路
开发语言·python
catchadmin10 小时前
PHP 8.7 协程新提案 让跨协程代码共享请求身份
开发语言·php
念何架构之路11 小时前
go-grpc设计思想
开发语言·后端·golang
酷炫码神12 小时前
Day 10|Pinia 状态管理:把散落在各组件的数据,收进一个全局仓库
android·开发语言·javascript
Elastic 中国社区官方博客12 小时前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es