乐于分享
好东西不私藏

Qt 插件化架构设计

Qt 插件化架构设计

一、目标

让你设计Qt插件的架构,你在怎么设计?你这个“插件间通信”系统,至少要满足:

插件解耦

  • 插件之间互不 include 头文件
  • 插件不直接持有对方指针

多对多通信

  • 1 个发送者 → N 个订阅者

线程安全

  • 插件可能在不同线程

可扩展

  • 消息类型可以不断增加
  • 插件可动态加载 / 卸载

Qt 生态友好

  • 用 Qt 的 event / signal / meta-object,而不是反 Qt

👉 结论这是一个 “轻量级消息总线(Message Bus / Event Bus)” 问题,而不是简单信号槽。


二、整体架构(全局图)

+-------------------+|   Plugin A        ||  (Publisher)      |+---------+---------+|          | publish(Message)          v+---------------------------+|      MessageBus           ||  - subscribe()            ||  - unsubscribe()          ||  - publish()              |+-----------+---------------+|            | dispatch            v+-------------------+   +-------------------+|   Plugin B        ||   Plugin C        || (Subscriber)     || (Subscriber)     |+-------------------+   +-------------------+

核心思想:

  • 所有插件 只依赖 MessageBus 接口
  • 插件之间完全不知道彼此存在

三、消息结构设计(重点)

3.1 不要直接用 QObject 传

❌ 错误设计:

emit sendData(QObject* data);

问题:

  • 类型不安全
  • 插件间 ABI 风险
  • 不利于序列化 / 记录 / 回放

3.2 推荐的消息结构(工程级)

① 消息头(统一)

enumclassMessageType : quint16 {    Invalid = 0,// 系统级    PluginLoaded,    PluginUnloaded,// 业务级    UserLogin,    DataUpdated,// ...};
structMessageHeader {    MessageType type;    quint32     senderId;     // 插件ID    quint64     timestamp;    // ms};

② 消息体(多态 / 变体)

方案一(强烈推荐):QVariantMap

structMessage {    MessageHeader header;    QVariantMap   payload;};

示例:

Message msg;msg.header.type = MessageType::UserLogin;msg.header.senderId = 1001;msg.payload["userId"] = 42;msg.payload["name"]   = "张三";

✅ 优点:

  • 插件完全解耦
  • ABI 安全
  • 可跨线程 / 可序列化
  • 非常适合插件系统

方案二(高性能):自定义 struct + QVariant

structUserLoginPayload {int userId;    QString name;};Q_DECLARE_METATYPE(UserLoginPayload)
Message msg;msg.payload["data"] = QVariant::fromValue(UserLoginPayload{42"Alice"});

⚠️ 适合性能敏感模块(图像 / 大数据)


四、MessageBus 设计(核心)

4.1 对外接口(纯虚,方便替换)

classIMessageBus {public:virtual ~IMessageBus() = default;virtualvoidsubscribe(        MessageType type,        QObject* receiver,constchar* slot    )0;virtualvoidunsubscribe(QObject* receiver)0;virtualvoidpublish(const Message& msg)0;};

4.2 订阅者内部结构

structSubscriber {    QPointer<QObject> receiver;    QByteArray        slot;};
QHash<MessageType, QList<Subscriber>> m_subscribers;QReadWriteLock m_lock;

4.3 publish 实现(线程安全 + Qt 友好)

void MessageBus::publish(const Message& msg){    QList<Subscriber> subs;    {QReadLocker locker(&m_lock);        subs = m_subscribers.value(msg.header.type);    }for (constauto& sub : subs) {if (!sub.receiver)continue;        QMetaObject::invokeMethod(            sub.receiver,            sub.slot.constData(),            Qt::QueuedConnection,   // 🔥 跨线程安全            Q_ARG(Message, msg)        );    }}

👉 关键点

  • QueuedConnection
     → 自动线程切换
  • QPointer
     → 插件卸载自动失效
  • 不在锁内 invoke,避免死锁

4.4 subscribe 实现

void MessageBus::subscribe(    MessageType type,    QObject* receiver,constchar* slot){QWriteLocker locker(&m_lock);    m_subscribers[type].append({receiver, slot});}

五、插件如何使用(非常干净)

5.1 插件接口

classIPlugin {public:virtual ~IPlugin() = default;virtualvoidinit(IMessageBus* bus)0;virtual quint32 pluginId()const0;};

5.2 插件订阅消息

void PluginB::init(IMessageBus* bus){    bus->subscribe(        MessageType::UserLogin,this,        SLOT(onUserLogin(Message))    );}
void PluginB::onUserLogin(const Message& msg){int userId = msg.payload["userId"].toInt();}

5.3 插件发布消息

Message msg;msg.header.type = MessageType::DataUpdated;msg.header.senderId = pluginId();msg.payload["value"] = 123;bus->publish(msg);

六、插件卸载安全性(关键)

6.1 插件销毁时

MessageBus::unsubscribe(QObject* receiver);

或依赖 QPointer 自动失效(推荐)


七、进阶设计

7.1 消息过滤(条件订阅)

using MessageFilter = std::function<bool(const Message&)>;
structSubscriber {    QPointer<QObject> receiver;    QByteArray slot;    MessageFilter filter;};

7.2 同步请求 / 响应(RPC 风格)

Message msg;msg.payload["requestId"] = QUuid::createUuid();

订阅方返回:

Message resp;resp.header.type = MessageType::Response;resp.payload["requestId"] = reqId;

7.3 调试神器:消息监控插件

subscribe(MessageType::Invalid, monitor, SLOT(onAnyMessage));

用来做日志、抓包、回放


八、为什么不用 Qt Signal / Slot 直接连?

方案
问题
插件间直接 connect
强耦合、难卸载
全局 QObject 信号
类型爆炸
MessageBus
✅ 解耦、可扩展、可监控

九、我给你的最终建议(老 Qt 工程师视角)

插件系统 = 消息总线 + 数据驱动,而不是信号槽满天飞

你这套设计:

  • 10 个插件 OK
  • 50 个插件 OK
  • 插件动态卸载不炸
  • 线程安全
  • 可单元测试
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Qt 插件化架构设计

猜你喜欢

  • 暂无文章