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 的动画方法。
# 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…