Qt Plugin创建及调用2–插件管理器
简述
Qt 本身提供了插件相关的技术,但并没有提供一个通用的插件框架!倘若要开发一个较大的 GUI 应用程序,并希望使其可扩展,那么拥有这样一个插件框架无疑会带来很大的好处。
插件系统构成
插件系统,可以分为三部分:
- 主系统
通过插件管理器加载插件,并创建插件对象。一旦插件对象被创建,主系统就会获得相应的指针/引用,它可以像任何其他对象一样使用。 - 插件管理器
用于管理插件的生命周期,并将其暴露给主系统。它负责查找并加载插件,初始化它们,并且能够进行卸载。它还应该让主系统迭代加载的插件或注册的插件对象。 - 插件
插件本身应符合插件管理器协议,并提供符合主系统期望的对象。
实际上,很少能看到这样一个相对独立的分离,插件管理器通常与主系统紧密耦合,因为插件管理器需要最终提供(定制)某些类型的插件对象的实例。
程序流
框架的基本程序流,如下所示:
插件管理器
上面提到,插件管理器有一个职责 - 加载插件。那么,是不是所有的插件都需要加载呢?当然不是!只有符合我们约定的插件,才会被认为是标准的、有效的插件,外来插件一律认定为无效。
为了解决这个问题,可以为插件设定一个“标识(Interface)” - PluginInterface.h
:
#ifndef PLUGININTERFACE_H
#define PLUGININTERFACE_H
#include <QStringList>
#include <QWidget>
class PluginInterface
{
public:
virtual ~PluginInterface() {}
public:
virtual void setInitData(QStringList &strlist) = 0;
virtual void getResultData(QStringList &strlist) = 0;
};
#define PluginInterface_iid "QtPluginsTest.QtPluginsManager.PluginInterface"
Q_DECLARE_INTERFACE(PluginInterface, PluginInterface_iid)
#endif // PLUGININTERFACE_H
后期实现的所有插件,都必须继承自 PluginInterface
,这样才会被认定是自己的插件,以防外部插件注入。
注意:使用 Q_DECLARE_INTERFACE
宏,将 PluginInterface
接口与标识符一起公开。
插件的基本约束有了,插件的具体实现插件管理器并不关心,它所要做的工作是加载插件、卸载插件、检测插件的依赖、以及扫描插件的元数据(Json 文件中的内容)。。。为了便于操作,将其实现为一个单例。
qtpluginmanager.h
内容如下:
#ifndef QTPLUGINSMANAGER_H
#define QTPLUGINSMANAGER_H
#include "qtpluginsmanager_global.h"
#include <QObject>
#include <QPluginLoader>
#include <QVariant>
class QtPluginsManagerPrivate;
class QTPLUGINSMANAGERSHARED_EXPORT QtPluginsManager : public QObject
{
Q_OBJECT
public:
QtPluginsManager();
~QtPluginsManager();
static QtPluginsManager *instance();
//加载所有插件
void loadAllPlugins();
//扫描JSON文件中的插件元数据
void scanMetaData(const QString &filepath);
//加载其中某个插件
void loadPlugin(const QString &filepath);
//卸载所有插件
void unloadAllPlugins();
//卸载某个插件
void unloadPlugin(const QString &filepath);
//获取所有插件
QList<QPluginLoader *> allPlugins();
//获取所有插件名称
QList<QVariant> allPluginsName();
//获取某个插件名称
QVariant getPluginName(QPluginLoader *loader);
private:
static QtPluginsManager *m_instance;
QtPluginsManagerPrivate *d;
};
可以看到,插件管理器中有一个 d
指针,它包含了插件元数据的哈希表。此外,由于其拥有所有插件的元数据,所以还为其赋予了另外一个职能 - 检测插件的依赖关系:
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实例
};
注意: 这里的 check()
是一个递归调用,因为很有可能存在“插件A”依赖于“插件B”,而“插件B”又依赖于“插件C”的连续依赖情况。
QtPluginsManagerPrivate
中的哈希表在初始化插件管理器时被填充:
void QtPluginsManager::loadAllPlugins()
{
QDir pluginsdir = QDir(qApp->applicationDirPath());
pluginsdir.cd("plugins");
QFileInfoList pluginsInfo = pluginsdir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
//初始化插件中的元数据
for(QFileInfo fileinfo : pluginsInfo)
scanMetaData(fileinfo.absoluteFilePath());
//加载插件
for(QFileInfo fileinfo : pluginsInfo)
loadPlugin(fileinfo.absoluteFilePath());
}
元数据的具体扫描由 scan()
负责:
void QtPluginsManager::scanMetaData(const QString &filepath)
{
//判断是否为库(后缀有效性)
if(!QLibrary::isLibrary(filepath))
return ;
//获取元数据
QPluginLoader *loader = new QPluginLoader(filepath);
QJsonObject json = loader->metaData().value("MetaData").toObject();
QVariant var = json.value("name").toVariant();
d->m_names.insert(filepath, json.value("name").toVariant());
d->m_versions.insert(filepath, json.value("version").toVariant());
d->m_dependencies.insert(filepath, json.value("dependencies").toArray().toVariantList());
delete loader;
loader = nullptr;
}
一旦所有元数据被扫描,便可以检查是否能够加载插件:
void QtPluginsManager::loadPlugin(const QString &filepath)
{
if(!QLibrary::isLibrary(filepath))
return;
//检测依赖
if(!d->check(filepath))
return;
//加载插件
QPluginLoader *loader = new QPluginLoader(filepath);
if(loader->load())
{
PluginInterface *plugin = qobject_cast<PluginInterface *>(loader->instance());
if(plugin)
{
d->m_loaders.insert(filepath, loader);
plugin->connect_information(this, SLOT(onPluginInformation(QString&)), true);
}
else
{
delete loader;
loader = nullptr;
}
}
}
注意: 这里用到了前面提到的标识 - PluginInterface
,只有 qobject_cast
转换成功,才会加载到主系统中,这可以算作是真正意义上的第一道防线。
实际上,在内部检查是通过调用 QtPluginManagerPrivate::check()
递归地查询依赖元数据来完成的。
bool QtPluginsManagerPrivate::check(const QString &filepath)
{
for(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))
{
QString strcons = "Missing dependency: "+ name.toString()+" for plugin "+path;
qDebug()<<Q_FUNC_INFO<<strcons;
QMessageBox::warning(nullptr, ("Plugins Loader Error"), strcons, QMessageBox::Ok);
return false;
}
//再检测插件版本
if(m_versions.value(path) != version)
{
QString strcons = "Version mismatch: " + name.toString() +" version "+m_versions.value(m_names.key(name)).toString()+
" but " + version.toString() + " required for plugin "+path;
qDebug()<<Q_FUNC_INFO<<strcons;
QMessageBox::warning(nullptr, "Plugins Loader Error", strcons, QMessageBox::Ok);
return false;
}
//最后检测被依赖的插件是否还依赖其他的插件
if(!check(path))
{
QString strcons = "Corrupted dependency: "+name.toString()+" for plugin "+path;
qDebug()<<Q_FUNC_INFO<<strcons;
QMessageBox::warning(nullptr, "Plugins Loader Error", strcons, QMessageBox::Ok);
return false;
}
}
return true;
}
插件卸载的过程正好相反:
void QtPluginsManager::unloadAllPlugins()
{
for(QString filepath : d->m_loaders.keys())
unloadPlugin(filepath);
}
而具体的卸载由 unloadPlugin()
来完成:
void QtPluginsManager::unloadPlugin(const QString &filepath)
{
QPluginLoader *loader = d->m_loaders.value(filepath);
//卸载插件,并从内部数据结构中移除
if(loader->unload())
{
d->m_loaders.remove(filepath);
delete loader;
loader = nullptr;
}
}
万事俱备,然后返回所有的插件,以便主系统访问:
QList<QPluginLoader *> QtPluginsManager::allPlugins()
{
return d->m_loaders.values();
}
这样,整个插件管理的机制已经建立起来了,万里长征第一步。。。那剩下的事基本就比较简单了!插件的编写、插件之间的交互。。。