
1. framework(共享库,plugin 与 host 都链接)
framework/plugininterface.h
c
#ifndef PLUGININTERFACE_H
#define PLUGININTERFACE_H
#include <QObject>
#include <QVariantMap>
// 插件抽象接口
class PluginInterface
{
public:
virtual ~PluginInterface() = default;
/// 插件初始化,加载完成调用
virtual bool pluginInit() = 0;
/// 插件卸载,释放资源(必须清理消息总线订阅、请求)
virtual void pluginUnload() = 0;
/// 可选:供主程序调用插件功能
virtual void doAction(const QString &action, const QVariantMap ¶ms) = 0;
};
#define PluginInterface_IID "com.demo.PluginInterface/1.0"
Q_DECLARE_INTERFACE(PluginInterface, PluginInterface_IID)
#endif // PLUGININTERFACE_H
framework/messagebus.h
c
#ifndef MESSAGEBUS_H
#define MESSAGEBUS_H
#include <QObject>
#include <QVariantMap>
#include <QString>
#include <QSet>
#include <QMutex>
#include <functional>
#if defined(FRAMEWORK_LIBRARY)
# define FRAMEWORK_EXPORT Q_DECL_EXPORT
#else
# define FRAMEWORK_EXPORT Q_DECL_IMPORT
#endif
class FRAMEWORK_EXPORT IMessageReceiver
{
public:
virtual ~IMessageReceiver() = default;
virtual void onMessage(const QString& topic, const QVariantMap& msgData, const QString& senderPluginId) = 0;
};
using ReplyCallback = std::function<void(const QVariantMap& replyData, bool success)>;
class FRAMEWORK_EXPORT MessageBus : public QObject
{
Q_OBJECT
public:
static MessageBus* instance();
void subscribe(IMessageReceiver* receiver, const QString& topic);
void unsubscribe(IMessageReceiver* receiver, const QString& topic);
void unsubscribeAll(IMessageReceiver* receiver);
Q_INVOKABLE void postMessage(const QString& topic, const QVariantMap& msgData, const QString& senderPluginId = "");
signals:
void sigDispatch(const QString& topic, const QVariantMap& msgData, const QString& senderPluginId);
private:
explicit MessageBus(QObject *parent = nullptr);
~MessageBus() override;
mutable QMutex m_mutex;
QHash<QString, QSet<IMessageReceiver*>> m_topicSubscribers;
QHash<IMessageReceiver*, QSet<QString>> m_receiverTopics;
};
class FRAMEWORK_EXPORT RequestReplyHelper : public QObject, public IMessageReceiver
{
Q_OBJECT
public:
explicit RequestReplyHelper(QObject* parent = nullptr);
~RequestReplyHelper() override;
void sendRequest(const QString& reqTopic,
const QVariantMap& reqData,
int timeoutMs,
ReplyCallback cb);
void sendReply(const QString& requestId, const QVariantMap& replyData);
void clearAllPending();
void onMessage(const QString& topic, const QVariantMap& msgData, const QString& senderPluginId) override;
protected:
void timerEvent(QTimerEvent *event) override;
private:
QString generateRequestId();
struct PendingRequest
{
QString requestId;
ReplyCallback callback;
quint64 timerId;
};
QMutex m_mutex;
QHash<QString, PendingRequest> m_pendingMap;
};
#endif // MESSAGEBUS_H
framework/messagebus.cpp
c
#include "messagebus.h"
#include <QMutexLocker>
#include <QMetaObject>
#include <QUuid>
#include <QTimerEvent>
static MessageBus* g_msgBus = nullptr;
static QMutex g_instanceMutex;
MessageBus* MessageBus::instance()
{
QMutexLocker lock(&g_instanceMutex);
if (!g_msgBus)
g_msgBus = new MessageBus();
return g_msgBus;
}
MessageBus::MessageBus(QObject *parent)
: QObject(parent)
{
connect(this, &MessageBus::sigDispatch, this, [this](const QString& topic, const QVariantMap& data, const QString& senderId)
{
QMutexLocker lk(&m_mutex);
if (!m_topicSubscribers.contains(topic)) return;
const auto& receivers = m_topicSubscribers[topic];
for (auto recv : receivers)
{
QMetaObject::invokeMethod(recv,
"onMessage",
Qt::QueuedConnection,
Q_ARG(QString, topic),
Q_ARG(QVariantMap, data),
Q_ARG(QString, senderId));
}
});
}
MessageBus::~MessageBus()
{
QMutexLocker lock(&m_mutex);
m_topicSubscribers.clear();
m_receiverTopics.clear();
}
void MessageBus::subscribe(IMessageReceiver *receiver, const QString &topic)
{
if (!receiver || topic.isEmpty()) return;
QMutexLocker lock(&m_mutex);
m_topicSubscribers[topic].insert(receiver);
m_receiverTopics[receiver].insert(topic);
}
void MessageBus::unsubscribe(IMessageReceiver *receiver, const QString &topic)
{
if (!receiver || topic.isEmpty()) return;
QMutexLocker lock(&m_mutex);
if (m_topicSubscribers.contains(topic))
{
m_topicSubscribers[topic].remove(receiver);
if (m_topicSubscribers[topic].empty())
m_topicSubscribers.remove(topic);
}
if (m_receiverTopics.contains(receiver))
{
m_receiverTopics[receiver].remove(topic);
if (m_receiverTopics[receiver].empty())
m_receiverTopics.remove(receiver);
}
}
void MessageBus::unsubscribeAll(IMessageReceiver *receiver)
{
if (!receiver) return;
QMutexLocker lock(&m_mutex);
if (!m_receiverTopics.contains(receiver)) return;
const auto& topics = m_receiverTopics[receiver];
for (const auto& t : topics)
{
if (m_topicSubscribers.contains(t))
{
m_topicSubscribers[t].remove(receiver);
if (m_topicSubscribers[t].empty())
m_topicSubscribers.remove(t);
}
}
m_receiverTopics.remove(receiver);
}
void MessageBus::postMessage(const QString &topic, const QVariantMap &msgData, const QString &senderPluginId)
{
emit sigDispatch(topic, msgData, senderPluginId);
}
// RequestReplyHelper
RequestReplyHelper::RequestReplyHelper(QObject *parent)
: QObject(parent)
{
MessageBus::instance()->subscribe(this, "sys/reply");
}
RequestReplyHelper::~RequestReplyHelper()
{
clearAllPending();
MessageBus::instance()->unsubscribe(this, "sys/reply");
}
QString RequestReplyHelper::generateRequestId()
{
return QUuid::createUuid().toString(QUuid::WithoutBraces);
}
void RequestReplyHelper::sendRequest(const QString &reqTopic, const QVariantMap &reqData, int timeoutMs, ReplyCallback cb)
{
QString reqId = generateRequestId();
QVariantMap outMsg = reqData;
outMsg["__requestId"] = reqId;
{
QMutexLocker lk(&m_mutex);
quint64 tid = 0;
if(timeoutMs > 0)
tid = startTimer(timeoutMs, Qt::OneShotTimer);
m_pendingMap.insert(reqId, {reqId, std::move(cb), tid});
}
MessageBus::instance()->postMessage(reqTopic, outMsg);
}
void RequestReplyHelper::sendReply(const QString &requestId, const QVariantMap &replyData)
{
QVariantMap replyMsg = replyData;
replyMsg["__requestId"] = requestId;
MessageBus::instance()->postMessage("sys/reply", replyMsg);
}
void RequestReplyHelper::clearAllPending()
{
QMutexLocker lk(&m_mutex);
for(auto& item : m_pendingMap)
{
if(item.timerId !=0) killTimer(item.timerId);
if(item.callback) item.callback({}, false);
}
m_pendingMap.clear();
}
void RequestReplyHelper::onMessage(const QString &topic, const QVariantMap &msgData, const QString& /*senderPluginId*/)
{
if(topic != "sys/reply") return;
QString reqId = msgData["__requestId"].toString();
if(reqId.isEmpty()) return;
ReplyCallback localCb;
quint64 timerId =0;
{
QMutexLocker lk(&m_mutex);
if(!m_pendingMap.contains(reqId)) return;
auto pr = m_pendingMap.take(reqId);
localCb = std::move(pr.callback);
timerId = pr.timerId;
}
if(timerId !=0) killTimer(timerId);
if(localCb) localCb(msgData, true);
}
void RequestReplyHelper::timerEvent(QTimerEvent *event)
{
quint64 tid = event->timerId();
ReplyCallback localCb;
QString eraseKey;
{
QMutexLocker lk(&m_mutex);
for(auto iter = m_pendingMap.begin(); iter != m_pendingMap.end(); ++iter)
{
if(iter.value().timerId == tid)
{
localCb = std::move(iter.value().callback);
eraseKey = iter.key();
m_pendingMap.erase(iter);
break;
}
}
}
if(!eraseKey.isEmpty())
{
killTimer(tid);
if(localCb) localCb({}, false);
}
}
2. plugin_a 动态插件(DLL,广播发送 + RPC 客户端)
plugin_a/plugina.h
c
#ifndef PLUGINA_H
#define PLUGINA_H
#include <QObject>
#include "plugininterface.h"
#include "messagebus.h"
class PluginA : public QObject, public PluginInterface, public IMessageReceiver
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "com.demo.PluginInterface/1.0")
Q_INTERFACES(PluginInterface)
public:
bool pluginInit() override;
void pluginUnload() override;
void doAction(const QString &action, const QVariantMap ¶ms) override;
void onMessage(const QString& topic, const QVariantMap& msgData, const QString& senderPluginId) override;
private:
RequestReplyHelper m_reqHelper;
};
#endif // PLUGINA_H
plugin_a/plugina.cpp
c
#include "plugina.h"
#include <QDebug>
bool PluginA::pluginInit()
{
MessageBus::instance()->subscribe(this, "demo/broadcast");
qDebug() << "[PluginA] init ok, subscribe demo/broadcast";
return true;
}
void PluginA::pluginUnload()
{
MessageBus::instance()->unsubscribeAll(this);
m_reqHelper.clearAllPending();
qDebug() << "[PluginA] unload, clean resource";
}
void PluginA::doAction(const QString &action, const QVariantMap &/*params*/)
{
if(action == "send_broadcast")
{
QVariantMap data;
data["msg"] = "From PluginA broadcast message";
data["value"] = 7788;
MessageBus::instance()->postMessage("demo/broadcast", data, "PluginA");
qDebug() << "[PluginA] send demo/broadcast";
}
else if(action == "call_rpc")
{
QVariantMap req{{"input_text","Hello RPC from PluginA"}};
qDebug() << "[PluginA] send RPC service/get_data";
m_reqHelper.sendRequest("service/get_data", req, 5000,
[](const QVariantMap& rep, bool ok)
{
qDebug()<<"\n==== PluginA RPC Callback ====";
if(!ok){
qDebug()<<"RPC timeout/cancel";
return;
}
qDebug()<<"reply data:" << rep;
});
}
}
void PluginA::onMessage(const QString &topic, const QVariantMap &msgData, const QString &senderPluginId)
{
qDebug()<<"\n==== PluginA receive broadcast ====";
qDebug()<<"topic:"<<topic<<" sender:"<<senderPluginId<<" data:"<<msgData;
}
3. plugin_b 动态插件(DLL,广播接收 + RPC 服务端)
plugin_b/pluginb.h
c
#ifndef PLUGINB_H
#define PLUGINB_H
#include <QObject>
#include "plugininterface.h"
#include "messagebus.h"
class PluginB : public QObject, public PluginInterface, public IMessageReceiver
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "com.demo.PluginInterface/1.0")
Q_INTERFACES(PluginInterface)
public:
bool pluginInit() override;
void pluginUnload() override;
void doAction(const QString &action, const QVariantMap ¶ms) override;
void onMessage(const QString& topic, const QVariantMap& msgData, const QString& senderPluginId) override;
private:
RequestReplyHelper m_reqHelper;
};
#endif // PLUGINB_H
plugin_b/pluginb.cpp
c
#include "pluginb.h"
#include <QDebug>
#include <QThread>
bool PluginB::pluginInit()
{
MessageBus::instance()->subscribe(this, "demo/broadcast");
MessageBus::instance()->subscribe(this, "service/get_data");
qDebug() << "[PluginB] init ok, subscribe demo/broadcast & service/get_data";
return true;
}
void PluginB::pluginUnload()
{
MessageBus::instance()->unsubscribeAll(this);
m_reqHelper.clearAllPending();
qDebug() << "[PluginB] unload, clean resource";
}
void PluginB::doAction(const QString &/*action*/, const QVariantMap &/*params*/)
{
}
void PluginB::onMessage(const QString &topic, const QVariantMap &msgData, const QString &senderPluginId)
{
if(topic == "demo/broadcast")
{
qDebug()<<"\n==== PluginB receive broadcast ====";
qDebug()<<"topic:"<<topic<<" sender:"<<senderPluginId<<" data:"<<msgData;
}
else if(topic == "service/get_data")
{
qDebug()<<"\n==== PluginB receive RPC request ====" << msgData;
QString reqId = msgData["__requestId"].toString();
QThread::msleep(200); // simulate business cost
QVariantMap reply;
reply["code"] = 0;
reply["result_text"] = "PluginB business done";
reply["origin_input"] = msgData["input_text"];
m_reqHelper.sendReply(reqId, reply);
qDebug() << "[PluginB] send rpc reply";
}
}
4. host_app 主程序(QPluginLoader 加载 dll,UI)
host_app/mainwindow.h
c
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QPluginLoader>
#include "plugininterface.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
private slots:
void slotLoadPlugins();
void slotUnloadPlugins();
void slotBtnSendBroadcast();
void slotBtnCallRpc();
void slotBtnHostSendMsg();
private:
Ui::MainWindow *ui;
QPluginLoader* m_loaderA = nullptr;
QPluginLoader* m_loaderB = nullptr;
PluginInterface* m_pluginA = nullptr;
PluginInterface* m_pluginB = nullptr;
};
#endif // MAINWINDOW_H
c
host_app/mainwindow.cpp
c
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "messagebus.h"
#include <QDebug>
#include <QFileInfo>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle("MessageBus QPluginLoader Demo");
}
MainWindow::~MainWindow()
{
slotUnloadPlugins();
delete ui;
}
void MainWindow::slotLoadPlugins()
{
if(m_pluginA || m_pluginB)
{
qDebug()<<"plugins already loaded";
return;
}
// 根据构建输出目录,dll与exe放在同一目录
QString dllA = QFileInfo("./plugin_a.dll").absoluteFilePath();
QString dllB = QFileInfo("./plugin_b.dll").absoluteFilePath();
m_loaderA = new QPluginLoader(dllA, this);
m_loaderB = new QPluginLoader(dllB, this);
QObject* objA = m_loaderA->instance();
QObject* objB = m_loaderB->instance();
if(!objA || !objB)
{
qDebug()<<"load plugin failed:"<< m_loaderA->errorString() << m_loaderB->errorString();
return;
}
m_pluginA = qobject_cast<PluginInterface*>(objA);
m_pluginB = qobject_cast<PluginInterface*>(objB);
if(m_pluginA) m_pluginA->pluginInit();
if(m_pluginB) m_pluginB->pluginInit();
qDebug()<<"==== all plugins loaded ====";
}
void MainWindow::slotUnloadPlugins()
{
if(m_pluginA)
{
m_pluginA->pluginUnload();
m_pluginA = nullptr;
}
if(m_pluginB)
{
m_pluginB->pluginUnload();
m_pluginB = nullptr;
}
if(m_loaderA)
{
m_loaderA->unload();
delete m_loaderA;
m_loaderA = nullptr;
}
if(m_loaderB)
{
m_loaderB->unload();
delete m_loaderB;
m_loaderB = nullptr;
}
qDebug()<<"==== plugins unloaded ====";
}
void MainWindow::slotBtnSendBroadcast()
{
if(m_pluginA)
m_pluginA->doAction("send_broadcast", {});
}
void MainWindow::slotBtnCallRpc()
{
if(m_pluginA)
m_pluginA->doAction("call_rpc", {});
}
void MainWindow::slotBtnHostSendMsg()
{
QVariantMap data{{"sys_msg","Message from host main app"}};
MessageBus::instance()->postMessage("demo/broadcast", data, "host");
qDebug()<<"[Host] send demo/broadcast";
}

