QT插件学习系列(三) 插件间通信

2,617 阅读3分钟

1、 概述

前两篇文章学完我们可以从0开始写一个 QT 插件了,也有自己的简易版插件框架了。但是例子中只给出了单个插件的加载与调用,我们实现插件化的目的是为了扩展性,实际项目中会存在插件A,插件B,插件C。而这些插件之间怎么通信 ? 或者说怎么建立一种良好的通信结构,这既是一项必不可少的工作,更是完善我们插件框架的重要里程碑。

在我们实现通信机制的时候,要考虑下面几点(不止在这儿,在任何时候都要这么做):

1、 耦合性要低。
2、 代码量要少。
3、 扩展性要强。
4、 封装、继承、多态。

我们要尽量符合面向对象原则,面向对象中,再面向接口编程,而不是面向实现。

通信包括插件和主程序之间的通信,也包括插件和插件之间的通信,首先,在插件里面是获取不到主程序的内容的(试想键盘能知道 windows 内核的东西吗,它们之间是主从设备的关系,即插件管理器和插件也是主从关系),其次插件 A 也不知道插件 B 的存在(键盘不知道鼠标的存在),即无依赖关系的插件之间 0 耦合。

下图展示了我们整个插件的通信机制: 插件通信

本想建立一个单例的消息中转站 Router,用于转发消息的,但是一想算了,既然是个 Demo 就简单点,直接用 PluginManager 类吧。

2、 实例

基于文章(二)2019-05-28 QT插件学习系列(二) 插件管理器 的实例,我们增加一个模拟场景,主界面有个按钮,点击打开插件 A 的界面,插件 A 上有个按钮,可以通过点击调用插件 B 的动画方法。

image

# pluginMetaData.h

#pragma once

#include <QObject>
#include <QVariant>
#include <QMap>
#include <QString>
/**
 * 插件通信消息封装体
 **/
struct PluginMetaData
{
    QString from;
    QString dest;
    int type;
    QMap<QString,QVariant> map;
    QObject *object = nullptr;
};
Q_DECLARE_METATYPE(PluginMetaData);

首先需要封装一个标准的通信数据包,便于统一方法参数。

#pragma once

#include <QString>
#include <QtPlugin>
#include <QObject>
#include "pluginMetaData.h"
//定义接口
class InterfacePlugin
{

public:
    virtual ~InterfacePlugin() {}
    virtual void recMsgfromManager(PluginMetaData) = 0;
    virtual void sendMsg2Manager(PluginMetaData)   = 0;
};


#define InterfacePlugin_iid "Test.Plugin.InterfacePlugin"

Q_DECLARE_INTERFACE(InterfacePlugin, InterfacePlugin_iid)

针对接口进行了改进,增加了两个虚函数,一个是接收 manager 的消息,一个是发送消息给 manager 让其进行转发。

# qtpluginmanager.cpp

void QtPluginsManager::initSignalAndSlot()
{
    auto plugins = allPlugins();
    foreach (auto loader, plugins)
    {
    // 每个插件发送消息到manager,然后由manager 根据 dest 字段转发
    connect(loader->instance(),SIGNAL(sendMsg2Manager(PluginMetaData)),this,SLOT(recMsgfromPlugin(PluginMetaData)));
    }
}

void QtPluginsManager::recMsgfromPlugin(PluginMetaData metadata)
{
    auto loader = getPlugin(metadata.dest);
    if(loader)
    {
        auto interface = qobject_cast<InterfacePlugin*>(loader->instance());;
        if(interface)
        {
            interface->recMsgfromManager(metadata);
        }
    }
}

pluginmanager 类也增加了两个方法,一个是在加载完所有插件后初始化和插件通信的信号槽,一个是收到插件发来的消息后根据 dest 字段转发给对应的插件。

# widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include "qtpluginmanager.h"
#include "interfaceplugin.h"
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    connect(ui->pushButtonA,SIGNAL(clicked()),this,SLOT(clickA()));
}

Widget::~Widget()
{
    delete ui;
}

void Widget::clickA()
{
   QPluginLoader *loader =  QtPluginsManager::getInstance().getPlugin("pluginA");
    if(loader)
    {
        auto obj = loader->instance();
        if(obj->isWidgetType())
        {
            QWidget *widget = qobject_cast<QWidget*>(obj);
            widget->show();
        }
    }
    else
    {
     qDebug() << "未能找到插件A";
    }
}


主界面的 cpp 文件。

# pluginA.h

#pragma once

#include "ui_pluginA.h"
#include <QtPlugin>
#include "../TTTT/interfaceplugin.h"
namespace Ui {
class PluginA;
}

class PluginA :public QWidget, public InterfacePlugin
{
    // programmer.json 插件的信息描述类
    Q_OBJECT
    Q_PLUGIN_METADATA(IID InterfacePlugin_iid FILE "programmer.json")
    Q_INTERFACES(InterfacePlugin)
public:
    explicit PluginA(QWidget *parent = nullptr);
    virtual  void recMsgfromManager(PluginMetaData)  Q_DECL_OVERRIDE;

public slots:
    void clickOpenImg();
    void click2Animation();
signals:
    // 信号发射由 moc 来完成
    void sendMsg2Manager(PluginMetaData) Q_DECL_OVERRIDE;
private:
    Ui::PluginA *ui;
};


插件 A,实现了 interface 的两个虚函数,一个作为槽函数,一个作为信号使用(每个插件都需要这么做,所以定义为虚函数)。

# pluginA.cpp

#include "pluginA.h"
#include <QtDebug>
#include <QFileDialog>
#include <QPixmap>
PluginA::PluginA(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::PluginA)
{
    ui->setupUi(this);
    connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(clickOpenImg()));
    connect(ui->pushButton2,SIGNAL(clicked()),this,SLOT(click2Animation()));
}

void PluginA::clickOpenImg()
{
    QString path = QFileDialog::getOpenFileName(this, tr("选择图片"), ".", tr("Image Files(*.jpg *.png)"));
    ui->label->setPixmap(QPixmap(path));
}

void PluginA::click2Animation()
{
        PluginMetaData metadata;
        metadata.from = "pluginA";
        metadata.dest = "pluginB";
        metadata.type = 1;
        metadata.object = ui->label;
        emit sendMsg2Manager(metadata);
}

void PluginA::recMsgfromManager(PluginMetaData metedata)
{
    qDebug() << "recMsgfromManager : " << metedata.from;
}

pluginB 就比较简单了,收到消息和参数后执行动画动作即可。

# pluginB.cpp

#include "pluginB.h"
#include <QtDebug>
#include <QPropertyAnimation>

PluginB::PluginB(QObject *parent) :
    QObject(parent)
{
}

void PluginB::recMsgfromManager(PluginMetaData metedata)
{
    qDebug() << "PluginB recMsgfromManager : ";
    qDebug() << "metedata.from:" << metedata.from;
    qDebug() << "metedata.dest:" << metedata.dest;
    qDebug() << "metedata.type:" << metedata.type;
    if(metedata.object)
    {
        animation((QWidget*)metedata.object);
        qDebug() << "metedata.object:" << metedata.object->objectName();
    }
}

void PluginB::animation(QWidget *widget)
{
    QPropertyAnimation *pScaleAnimation = new QPropertyAnimation(widget, "geometry");
    pScaleAnimation->setDuration(1000);
    pScaleAnimation->setStartValue(QRect(0, 0, 0, 0));
    pScaleAnimation->setEndValue(QRect(100, 100, 200, 200));
    pScaleAnimation->start();
}

Demo 地址:
download.csdn.net/download/u0…
github.com/qht10030778…