QPluginLoader 动态 DLL 插件版,支持插件之间通信,广播。请求和应答

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 &params) = 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 &params) 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 &params) 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";
}
相关推荐
老赵的博客4 小时前
c++ QT之动态库加载问题
c++·qt
(Charon)4 小时前
【C++】定时器进阶:使用最小堆管理定时任务
c++·算法
hansang_IR4 小时前
【题解】 [省选联考 2021 A/B 卷] 卡牌游戏
c++·算法
lzx_0025 小时前
C++11(一)
开发语言·c++·算法
小七在进步6 小时前
C++入门(2)
java·jvm·c++
en.en..7 小时前
C语言核心解析:#define与typedef本质区别
开发语言·c++·算法
码匠许师傅7 小时前
【设计模式精讲】26.策略模式(Strategy)
c++·设计模式·策略模式·uml
果果燕8 小时前
实习笔记(六)主机按钮状态上报完整流程
开发语言·c++·php
是个西兰花8 小时前
网络基础1
linux·网络·c++·智能路由器