1、 概述
上文中展示了一个简单的 QT Low-API 插件实例,但是这却满足不了大型应用程序的实际场景,没有扩展性。而插件间的通信、加载卸载(释放内存)、插件元数据、插件生命周期、插件依赖等问题,便是我们要做的。在QT内部,高级 API 有 PluginManager 负责做这些事,但是低级 API 就需要自己写插件管理器来帮助我们解决这些问题。
想象一台 windows 系统的电脑,包含了主机、显示屏、键鼠等部件。假如我们拔掉键盘,电脑不会出错,只是缺失了键盘的功能,因此键盘就可以看做是一个插件。与此同时一台完整的电脑不仅包含了键鼠,还有耳机、音响、光驱、显卡等部件,这些部件其实都可以看成插件。对 windows 来说,这些“插件”都有一个管理者,即为设备管理器。设备管理器负责添加和删除电脑所有的硬件和驱动,因此可以将设备管理器理解为插件管理器。最后一点电脑系统都有自己的内核,一个 windows 系统从启动到关机都是内核在响应,而内核就可以看做加载插件的主程序,仿佛:“一旦你插上,我就能用你来打游戏”。
通过上面的例子可以看到,一个插件化系统从大的方面来说包含三方面:
上图中红色圈的地方可引申出一层,即对应 windows 的驱动层,负责和内核通信。而对应到插件系统即为 “Adapter”,负责插件通信,可避免插件间的相互依赖耦合。这个下篇再讲。
2、 实例
首先,下面的代码及类名都是模仿 QT 源码进行编写(当然也存在些许不完善):
#qtpluginmanager.h
#ifndef QTPLUGINSMANAGER_H
#define QTPLUGINSMANAGER_H
#include <QObject>
#include <QPluginLoader>
#include <QVariant>
#include "qtpluginsmanagerprivate.h"
#include <QDir>
class QtPluginsManager : public QObject
{
Q_OBJECT
public:
static QtPluginsManager &getInstance()
{
static QtPluginsManager m_instance;
return m_instance;
}
public:
// 获取插件目录
QDir getPluginPath();
//加载所有插件
void loadAllPlugins();
//扫描JSON文件中的插件元数据
void scan(const QString &filepath);
//加载其中某个插件
void loadPlugin(const QString &filepath);
//卸载所有插件
void unloadAllPlugins();
//卸载某个插件
void unloadPlugin(const QString &filepath);
//获取所有插件
QList<QPluginLoader *> allPlugins();
//获取某个插件
QPluginLoader* getPlugin(const QString &name);
private:
QtPluginsManager()
{
d = new QtPluginsManagerPrivate;
};
QtPluginsManagerPrivate *d;
};
#endif
# qtpluginsmanagerprivate.h
#ifndef QTPLUGINSMANAGERPRIVATE_H
#define QTPLUGINSMANAGERPRIVATE_H
#include <QString>
#include <QVariant>
#include <QDebug>
#include <QPluginLoader>
class QtPluginsManagerPrivate
{
public:
//插件依赖检测
bool check(const QString &filepath);
QHash<QString, QVariant> m_names; //插件路径--插件名称
QHash<QString, QVariant> m_versions; //插件路径--插件版本
QHash<QString, QVariantList>m_dependencies; //插件路径--插件额外依赖的其他插件
QHash<QString, QPluginLoader *>m_loaders; //插件路径--QPluginLoader实例
};
#endif
类 QtPluginsManagerPrivate 包含了所有插件的元数据集合,即 metadata,这些元数据来自于我们提前定义的 json 文件,且后缀必须为“.json”。对 QT 内部来说,此 json 文件由宏 Q_PLUGIN_METADATA 声明元数据为插件实例化的一部分。但更重要的是 m_dependencies 集合,包含了插件的依赖关系,以此决定插件的加载顺序和是否有必要加载。
#qtpluginmanager.cpp
void QtPluginsManager::loadAllPlugins()
{
QDir path = QDir(qApp->applicationDirPath());
path.cd("../plugins");
foreach (QFileInfo info, path.entryInfoList(QDir::Files | QDir::NoDotAndDotDot))
{
// 扫描所有插件,录入元数据信息
scan(info.absoluteFilePath());
}
foreach (QFileInfo info, path.entryInfoList(QDir::Files | QDir::NoDotAndDotDot))
{
// 加载插件
loadPlugin(info.absoluteFilePath());
}
}
#qtpluginmanager.cpp
// 扫描 JSON 文件中的插件元数据
void QtPluginsManager::scan(const QString& path)
{
/**
* 判断是否是库,排除pdb等垃圾文件
* Windows:.dll、.DLL
* Unix/Linux:.so
**/
if (!QLibrary::isLibrary(path))
return;
// 获取元数据(名称、版本、依赖)
QPluginLoader *loader = new QPluginLoader(path);
QJsonObject json = loader->metaData().value("MetaData").toObject();
d->m_names.insert(path, json.value("name").toVariant());
d->m_versions.insert(path, json.value("version").toVariant());
d->m_dependencies.insert(path, json.value("dependencies").toArray().toVariantList());
delete loader;
loader = Q_NULLPTR;
}
#qtpluginmanager.cpp
// 加载插件
void QtPluginsManager::loadPlugin(const QString& path)
{
// 判断是否是库
if (!QLibrary::isLibrary(path))
return;
// 检测插件依赖(递归调用)
if (!d->check(path))
return;
// 加载插件
QPluginLoader loader = new QPluginLoader(path);
if (loader->load()) {
// 如果继承自 Plugin,则认为是自己的插件(防止外部插件注入)。
InterfacePlugin *plugin = qobject_cast<InterfacePlugin *>(loader->instance());
if (plugin)
{
d->m_loaders.insert(path, loader);
plugin->output("plugin");
}
else
{
delete loader;
loader = Q_NULLPTR;
}
}
}
# qtpluginsmanagerprivate.cpp
#include "qtpluginsmanagerprivate.h"
// 检测插件依赖
bool QtPluginsManagerPrivate::check(const QString& path)
{
bool status = true;
foreach (QVariant item, m_dependencies.value(path)) {
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;
}
QT 内部对于插件的版本管理比较严格,作为 Demo 暂不深入讨论。 详见:Qt Documentation Snapshots :Plugin Meta Data。
check 方法检查了是否缺失了某些依赖插件,检查了插件版本是否匹配,假如 A 依赖了B,B 依赖了 C,倘若 C 插件缺失了,则 A 插件也就无法正确加载(因为有可能在 A 的构造函数中用到了 B,B 的构造函数中用到了 C),因此可见,插件还是尽可能不要产生依赖。
参考 :
一去丶二三里:构建自己的 Qt 插件系统
深入理解QtCreator的插件设计架构
Demo 地址:
download.csdn.net/download/u0…
github.com/qht10030778…