文章目录
一、前言
插件开发总结–插件的创建及使用一文中,展示了在Qt中如何使用Qt Low-API插件实例。但是这却满足不了大型应用程序的实际场景,没有扩展性。而插件间的通信、加载卸载(释放内存)、插件元数据、插件生命周期、插件依赖等问题,便是我们要做的。在QT内部,高级 API 有 PluginManager 负责做这些事,但是低级 API 就需要自己写插件管理器来帮助我们解决这些问题。
想象一台 windows 系统的电脑,包含了主机、显示屏、键鼠等部件。假如我们拔掉键盘,电脑不会出错,只是缺失了键盘的功能,因此键盘就可以看做是一个插件。与此同时一台完整的电脑不仅包含了键鼠,还有耳机、音响、光驱、显卡等部件,这些部件其实都可以看成插件。对 windows 来说,这些“插件”都有一个管理者,即为设备管理器。设备管理器负责添加和删除电脑所有的硬件和驱动,因此可以将设备管理器理解为插件管理器。最后一点电脑系统都有自己的内核,一个 windows 系统从启动到关机都是内核在响应,而内核就可以看做加载插件的主程序,仿佛:“一旦你插上,我就能用你来打游戏”。
二、项目结构
上文中的程序,主程序和插件程序是分开的,不便管理,本文中采取父子工程结构,如上图,创建流程如下:
1、创建子目录项目
2、创建子项目
Main、pluginA、pluginB都是子项目
PluginInterface.h
#ifndef
#define
#include <QObject>
#include <QJsonObject>
struct PluginMetaData
{
QString from; //消息来源
QString dest; //消息去向
QString msg; //消息
QObject* object = nullptr;
QJsonObject info = QJsonObject();
};
Q_DECLARE_METATYPE(PluginMetaData); //确保类型可以通过信号槽传递
//定义接口
class PluginInterface
{
public:
virtual ~PluginInterface(){}
virtual void recMsgFromManager(PluginMetaData) = 0; //接收来自插件管理器的消息
virtual void sendMsgToManager(PluginMetaData) = 0; //发生消息到插件管理器
};
//一定是唯一的标识符
#define
QT_BEGIN_NAMESPACE
Q_DECLARE_INTERFACE(PluginInterface,PluginInterface_iid)
QT_END_NAMESPACE
#endif// PLUGININTERFACE_H
widget.h
#ifndef
#define
#include <QWidget>
#include <QMessageBox>
#include <QDebug>
#include "PluginInterface.h"
#include "PluginManager.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
};
#endif// WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
PluginManager::instance()->loadAllPlugins();
//qDebug()<<"allPluginsName: "<<PluginManager::instance()->allPluginsName();
QPluginLoader *loader1 = PluginManager::instance()->getPlugin("pluginA");
if(loader1) {
PluginInterface *pluginA = dynamic_cast<PluginInterface*>(loader1->instance());
if(pluginA) {
PluginMetaData m;
m.dest = "pluginB";
m.from = "pluginA";
m.msg = "插件A发给插件B的消息";
pluginA->sendMsgToManager(m);
}
}
QPluginLoader *loader2 = PluginManager::instance()->getPlugin("pluginB");
if(loader2) {
PluginInterface *pluginB = dynamic_cast<PluginInterface*>(loader2->instance());
if(pluginB) {
PluginMetaData m;
m.dest = "pluginA";
m.from = "pluginB";
m.msg = "插件B发给插件A的消息";
pluginB->sendMsgToManager(m);
}
}
}
Widget::~Widget()
{
delete ui;
}
main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
pluginA.pro
QT += widgets
TEMPLATE = lib #表明这个makefile是一个lib的makefile
CONFIG += plugin #应用程序是一个插件
TARGET = pluginA #插件名称
DESTDIR = ../plugins # 输出目录
HEADERS += \
pluginA.h
SOURCES += \
pluginA.cpp
DISTFILES += \
pluginA.json
pluginA.h
#ifndef
#define
#include <QObject>
#include <QtPlugin>
#include <QDebug>
#include "../Main/PluginInterface.h"
class PluginA : public QObject,public PluginInterface
{
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID PluginInterface_iid FILE "pluginA.json")
public:
explicit PluginA(QObject *parent = nullptr);
void show_pluginA();
void recMsgFromManager(PluginMetaData metaData);
signals:
void sendMsgToManager(PluginMetaData);
};
#endif// PLUGINA_H
pluginA.cpp
#include "pluginA.h"
PluginA::PluginA(QObject *parent) : QObject(parent)
{
}
void PluginA::show_pluginA()
{
qDebug()<<"这是插件A";
}
void PluginA::recMsgFromManager(PluginMetaData metaData)
{
qDebug()<<"插件A接收到消息:"<<metaData.msg;
}
pluginB.pro
QT += widgets
TEMPLATE = lib #表明这个makefile是一个lib的makefile
CONFIG += plugin #应用程序是一个插件
TARGET = pluginB #插件名称
DESTDIR = ../plugins # 输出目录
HEADERS += \
pluginB.h
SOURCES += \
pluginB.cpp
DISTFILES += \
pluginB.json
pluginB.h
#ifndef
#define
#include <QObject>
#include <QtPlugin>
#include <QDebug>
#include "../Main/PluginInterface.h"
class PluginB : public QObject,public PluginInterface
{
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID PluginInterface_iid FILE "pluginB.json")
public:
explicit PluginB(QObject *parent = nullptr);
void show_pluginB();
void recMsgFromManager(PluginMetaData metaData);
signals:
void sendMsgToManager(PluginMetaData);
};
#endif// PLUGINB_H
pluginB.cpp
#include "pluginB.h"
PluginB::PluginB(QObject *parent) : QObject(parent)
{
}
void PluginB::show_pluginB()
{
qDebug()<<"这是插件B";
}
void PluginB::recMsgFromManager(PluginMetaData metaData)
{
qDebug()<<"插件B接收到消息:"<<metaData.msg;
}
三、创建插件管理器文件
在主程序Main项目中创建PluginManager类
#ifndef
#define
#include "PluginInterface.h"
#include <QObject>
#include <QPluginLoader>
#include <QVariant>
class PluginManager : public QObject
{
Q_OBJECT
public:
explicit PluginManager(QObject *parent = nullptr);
~PluginManager();
static PluginManager *instance(){
if(m_instance==nullptr)
m_instance=new PluginManager();
return m_instance;
}
//扫描JSON文件中的插件元数据
void scanMetaData(const QString &filepath);
//加载所有插件
void loadAllPlugins();
//加载其中某个插件
void loadPlugin(const QString &filepath);
//卸载所有插件
void unloadAllPlugins();
//卸载某个插件
void unloadPlugin(const QString &filepath);
//获取所有插件名称
QList<QVariant> allPluginsName();
//获取所有插件
QList<QPluginLoader *> allPlugins();
//获取某个插件名称
QVariant getPluginName(QPluginLoader *loader);
//根据名称获得插件
QPluginLoader* getPlugin(const QString &name);
public slots:
void recMsgFromPlugin(PluginMetaData);
private:
static PluginManager *m_instance;
class PluginsManagerPrivate;
PluginsManagerPrivate *managerPrivate;
};
#endif// PLUGINMANAGER_H
#include "PluginManager.h"
#include <QDir>
#include <QCoreApplication>
#include <QJsonArray>
#include <QDebug>
PluginManager* PluginManager::m_instance=nullptr;
class PluginManager::PluginsManagerPrivate
{
public:
PluginsManagerPrivate()
{
m_names.clear();
m_versions.clear();
m_dependencies.clear();
m_loaders.clear();
}
~PluginsManagerPrivate(){}
QHash<QString, QVariant> m_names; //插件路径--插件名称
QHash<QString, QVariant> m_versions; //插件路径--插件版本
QHash<QString, QVariantList> m_dependencies; //插件路径--插件额外依赖的其他插件
QHash<QString, QPluginLoader *> m_loaders; //插件路径--QPluginLoader实例
bool check(const QString &filepath) //插件依赖检测
{
bool status = true;
foreach (QVariant item, m_dependencies.value(filepath)) {
QVariantMap map = item.toMap();
// 依赖的插件名称、版本、路径
QVariant name = map.value("name");
QVariant version = map.value("version");
QString path = m_names.key(name);
/********** 检测插件是否依赖于其他插件 **********/
// 先检测插件名称
if (!m_names.values().contains(name)) {
qDebug() << Q_FUNC_INFO << " Missing dependency:" << name.toString() << "for plugin" << path;
status = false;
continue;
}
// 再检测插件版本
if (m_versions.value(path) != version) {
qDebug() << Q_FUNC_INFO << " Version mismatch:" << name.toString() << "version"
<< m_versions.value(m_names.key(name)).toString() << "but" << version.toString() << "required for plugin" << path;
status = false;
continue;
}
// 然后,检测被依赖的插件是否还依赖于另外的插件
if (!check(path)) {
qDebug() << Q_FUNC_INFO << "Corrupted dependency:" << name.toString() << "for plugin" << path;
status = false;
continue;
}
}
return status;
}
};
PluginManager::PluginManager(QObject *parent) : QObject(parent)
{
managerPrivate = new PluginsManagerPrivate;
}
PluginManager::~PluginManager()
{
delete managerPrivate;
}
void PluginManager::loadAllPlugins()
{
QDir pluginsDir(qApp->applicationDirPath()); //pluginsDir: "../build-xxx-debug/debug"
if(pluginsDir.dirName().toLower() == "debug" ||
pluginsDir.dirName().toLower() == "release") {
pluginsDir.cdUp(); //pluginsDir: "../build-xxx-debug"
pluginsDir.cdUp(); //pluginsDir: "../"
}
pluginsDir.cd("plugins");
QFileInfoList pluginsInfo = pluginsDir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
//初始化插件中的元数据
for(QFileInfo fileinfo : pluginsInfo){
//qDebug()<<"loadAllPlugins:"<<fileinfo.absoluteFilePath();
scanMetaData(fileinfo.absoluteFilePath());
}
//加载插件
for(QFileInfo fileinfo : pluginsInfo)
loadPlugin(fileinfo.absoluteFilePath());
}
void PluginManager::scanMetaData(const QString &filepath)
{
//判断是否为库(后缀有效性)
if(!QLibrary::isLibrary(filepath))
return ;
//获取元数据
QPluginLoader *loader = new QPluginLoader(filepath);
//qDebug()<<loader->metaData().keys();
QJsonObject json = loader->metaData().value("MetaData").toObject();
// for(int i=0; i<json.keys().size(); ++i) {
// qDebug()<<json.keys().at(i)<< " : "<<json.value(json.keys().at(i));
// }
managerPrivate->m_names.insert(filepath, json.value("name").toVariant());
managerPrivate->m_versions.insert(filepath, json.value("version").toVariant());
managerPrivate->m_dependencies.insert(filepath, json.value("dependencies").toArray().toVariantList());
delete loader;
loader = nullptr;
}
void PluginManager::loadPlugin(const QString &filepath)
{
if(!QLibrary::isLibrary(filepath))
return;
//检测依赖
if(!managerPrivate->check(filepath))
return;
//加载插件
QPluginLoader *loader = new QPluginLoader(filepath);
if(loader->load()) {
PluginInterface *plugin = qobject_cast<PluginInterface *>(loader->instance());
if(plugin) {
managerPrivate->m_loaders.insert(filepath, loader);
connect(loader->instance(),SIGNAL(sendMsgToManager(PluginMetaData)),
this,SLOT(recMsgFromPlugin(PluginMetaData)));
}else {
delete loader;
loader = nullptr;
}
}else{
qDebug()<<"loadPlugin:"<<filepath<<loader->errorString();
}
}
void PluginManager::unloadAllPlugins()
{
for(QString filepath : managerPrivate->m_loaders.keys())
unloadPlugin(filepath);
}
void PluginManager::unloadPlugin(const QString &filepath)
{
QPluginLoader *loader = managerPrivate->m_loaders.value(filepath);
//卸载插件,并从内部数据结构中移除
if(loader->unload()) {
managerPrivate->m_loaders.remove(filepath);
delete loader;
loader = nullptr;
}
}
QList<QPluginLoader *> PluginManager::allPlugins()
{
return managerPrivate->m_loaders.values();
}
QList<QVariant> PluginManager::allPluginsName()
{
return managerPrivate->m_names.values();
}
QVariant PluginManager::getPluginName(QPluginLoader *loader)
{
if(loader)
return managerPrivate->m_names.value(managerPrivate->m_loaders.key(loader));
else
return "";
}
QPluginLoader *PluginManager::getPlugin(const QString &name)
{
return managerPrivate->m_loaders.value(managerPrivate->m_names.key(name));
}
void PluginManager::recMsgFromPlugin(PluginMetaData metaData)
{
auto loader = getPlugin(metaData.dest); //目标插件
if(loader) {
auto interface = qobject_cast<PluginInterface*>(loader->instance());
if(interface) {
interface->recMsgFromManager(metaData); //转发给对应的插件
}
}
}
四、插件管理器的使用
直接编译运行,就会生成插件在plugins文件夹中,无须移动,程序加载插件会直接去此文件夹中加载插件
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
PluginManager::instance()->loadAllPlugins(); //加载所有插件
//qDebug()<<"allPluginsName: "<<PluginManager::instance()->allPluginsName();
QPluginLoader *loader1 = PluginManager::instance()->getPlugin("pluginA");
if(loader1) {
PluginInterface *pluginA = dynamic_cast<PluginInterface*>(loader1->instance());
if(pluginA) {
PluginMetaData m;
m.dest = "pluginB";
m.from = "pluginA";
m.msg = "插件A发给插件B的消息";
pluginA->sendMsgToManager(m);
}
}
QPluginLoader *loader2 = PluginManager::instance()->getPlugin("pluginB");
if(loader2) {
PluginInterface *pluginB = dynamic_cast<PluginInterface*>(loader2->instance());
if(pluginB) {
PluginMetaData m;
m.dest = "pluginA";
m.from = "pluginB";
m.msg = "插件B发给插件A的消息";
pluginB->sendMsgToManager(m);
}
}
}