Qt 插件化架构设计
一、目标
让你设计Qt插件的架构,你在怎么设计?你这个“插件间通信”系统,至少要满足:
1 插件解耦
-
插件之间互不 include 头文件 - 插件不直接持有对方指针
2 多对多通信
- 1 个发送者 → N 个订阅者
3 线程安全
- 插件可能在不同线程
4 可扩展
- 消息类型可以不断增加
- 插件可动态加载 / 卸载
5 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()const= 0;};
. 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 直接连?
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
九、我给你的最终建议(老 Qt 工程师视角)
插件系统 = 消息总线 + 数据驱动,而不是信号槽满天飞
你这套设计:
- 10 个插件 OK
- 50 个插件 OK
- 插件动态卸载不炸
- 线程安全
- 可单元测试
夜雨聆风