教程源码:https://github.com/myhhub/qtPluginApp
初识QtPlugin
概述
为什么我们要学习插件化,其和 windows 导出 dll 有什么区别呢?
- 导出的动态库如果缺失,程序不能运行。但插件可以。
- 同一套代码,即可分别在 windows 下和 linux 下生成插件。
QT 本身提供两种插件支持,一种称为高级 API,一种称为低级 API。
- 高级API的作用是扩展 QT 程序本身,需要子类化 QT 提供的插件基类,例如现有的 QTSqlDriver,因此你可也以编写自己的 QTStyle 扩展 QT。
- 低级 API 的作用是扩展自己的程序,也就是动态库的形式,在windows下就是个dll。同时因为高级 API 基于低级 API 创建,因此掌握低级 API 用法,高级 API 的用法也不在话下。
QT 的插件加载需要用到类 QPluginloader,你可以编写支持任意功能的插件。如何创建一个插件和加载这个插件 QT Assist 中是这样描述的:
创建一个扩展应用程序的插件:
- 定义一组用于与插件对话的接口(仅具有纯虚拟函数的类)。(预留好接口,方便建立通信)。
- 接口内使用 Q_DECLARE_INTERFACE() 宏告诉qt的元对象系统有关接口的信息。
- 使用 QPluginLoader 加载插件。
- 使用 qobject_cast 检验插件是否实现了既定的接口(以防外部插件注入),转换成功即可得到插件实例。
插件编写具体步骤(代码编写):
- 声明插件类,并继承 QObject 和 实现上面提到的既定接口。
- 使用 Q_INTERFACES 告诉 QT 元对象系统有关这个接口的信息(虚方法)。
- 使用 Q_PLUGIN_METADATA 导出插件。(Q_PLUGIN_METADATA 是 QT5的宏,QT4 使用的是 Q_EXPORT_PLUGIN2)
- 编写合适的 .pro 文件。
实例
1、 新建子目录项目 PluginApp
2、 在 PluginApp 下 新建 QWidget 项目,名为 Main
3、 右键 PluginApp 新建子项目 pluginA
4、 编写接口,在 Main 下新建头文件 interfaceplugin.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #ifndef INTERFACEPLUGIN_H #define INTERFACEPLUGIN_H #include <QString> #include <QtPlugin>
class InterfacePlugin { public: virtual ~InterfacePlugin() {} virtual QString output(const QString &message) = 0; }; #define InterfacePlugin_iid "Test.Plugin.InterfacePlugin" Q_DECLARE_INTERFACE(InterfacePlugin, InterfacePlugin_iid) #endif
|
5、 加载插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include "widget.h" #include <QApplication> #include <QDir> #include <QPluginLoader> #include "interfaceplugin.h" #include <QObject> #include <QDebug> int main(int argc, char *argv[]) { QApplication a(argc, argv);
QDir path = QDir(qApp->applicationDirPath()); path.cd("plugins"); foreach (QFileInfo info, path.entryInfoList(QDir::Files | QDir::NoDotAndDotDot)) { QPluginLoader pluginLoader(info.absoluteFilePath()); QObject *plugin = pluginLoader.instance(); if (plugin) { InterfacePlugin *app = qobject_cast<InterfacePlugin*>(plugin); if (app) { QJsonObject json = pluginLoader.metaData().value("MetaData").toObject(); qDebug() << "********** MetaData **********"; qDebug() << json.value("author").toVariant(); qDebug() << json.value("date").toVariant(); qDebug() << json.value("name").toVariant(); qDebug() << json.value("version").toVariant(); qDebug() << json.value("dependencies").toArray().toVariantList();
app->output("i am a plugin"); } } } return a.exec(); }
|
6、 编写插件 .pro 文件、头文件、 cpp 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| TEMPLATE = lib CONFIG += plugin TARGET = pluginA win32 { CONFIG(debug, debug|release) { DESTDIR = ../Main/debug/plugins } else { DESTDIR = ../Main/release/plugins } } HEADERS += \ pluginA.h SOURCES += \ pluginA.cpp DISTFILES += \ programmer.json
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #ifndef PLUGINA_H #define PLUGINA_H #include <QObject> #include <QtPlugin> #include "../Main/interfaceplugin.h" class PluginA : public QObject, public InterfacePlugin { Q_OBJECT Q_PLUGIN_METADATA(IID InterfacePlugin_iid FILE "programmer.json") Q_INTERFACES(InterfacePlugin) public: explicit PluginA(QObject *parent = 0); virtual QString output(const QString &message) Q_DECL_OVERRIDE; }; #endif
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include "pluginA.h" #include <QtDebug> PluginA::PluginA(QObject *parent) : QObject(parent) { } QString PluginA::output(const QString &message) { qDebug() << message + "插件A加载成功"; return message; }
|
“programmer.json” 为插件描述文件,会作为元数据被加载到插件中,可在需要的时候手动读取。其中包含了插件的基本信息,更重要的是包含了插件的依赖项,这些依赖项决定了插件的加载顺序,关于插件依赖解决后面再讲。
1 2 3 4 5 6 7 8
| { "author" : "myh", "date" : "2021/04/22", "name" : "pluginA", "version" : "1.0.0", "des" : "这是一个插件A,按此方法加载插件B、C等", "dependencies" : [] }
|
注:
1、Main 项目选择 QWidget GUI项目是有原因的,下篇再说是为什么。
2、windows 下生成的插件为 dll 后缀,linux 下生成的即为 .so后缀(下篇出 linux 测试结果)。
Qt插件的源码中,基本都能见到一个 xxx.json 的文件,这个文件中通常只包含一句:
1 2 3
| { "Keys": [ "yyy" ] }123
|
我们可以猜到这个文件中的”Keys”应该是指定了与插件相关的关键字。那这个 .json 文件到底是如何起作用的?先来认识一下 JSON 。
JSON是一种存储结构化数据的格式,它有6中基本数据类型,分别是:
- bool 布尔型,取值可以是 true 或 false
- double 数字类型
- string 字符串类型
- array 数组类型
- object 对象类型
- null 空类型
具体可参见 Qt Assistant 中关于”JSON Support in Qt “的介绍。
A simple JSON document encoding a person, his/her age, address and phone numbers could look like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 1. { 2. "FirstName": "John", # FirstName是变量(字段)的名称;John是变量的值 3. "LastName": "Doe", 4. "Age": 43, 5. "Address": { 6. "Street": "Downing Street 10", 7. "City": "London", 8. "Country": "Great Britain" 9. }, 10. "Phone numbers": [ 11. "+44 1234567", 12. "+44 2345678" 13. ] 14. }
|
值得一提的是,数组类型的字段在.json文件中赋值时应该用方括号 ‘[’ 和 ‘]’ 括起来,对象类型的字段在赋值时应用花括号 ‘{’ 和 ‘}’ 括起来,普通类型的数据则不需要括。每一个 .json 文件描述了一个 JSON对象,而一个JSON对象中的对象类型字段,又可以看做是一个子JSON对象(JSON对象的嵌套)。
.json在Qt插件中主要用于存储Qt插件的元信息(metaData),在Qt中,有一个专门的类 QJsonObject 来描述一个JSON。
Qt中的JSON相关类
QJsonArray |
Encapsulates a JSON array |
封装JSON数组 |
QJsonDocument |
Way to read and write JSON documents |
读取和写入JSON文本的方式 |
QJsonObject |
Encapsulates a JSON object |
封装JSON对象 |
QJsonObject::iterator |
QJsonObject::iterator class provides an STL-style non-const iterator for QJsonObject |
JSON迭代器 |
QJsonObject::const_iterator |
QJsonObject::const_iterator class provides an STL-style const iterator for QJsonObject |
JSON const迭代器 |
QJsonParseError |
Used to report errors during JSON parsing |
用于报告JSON解析期间的错误 |
QJsonValue |
Encapsulates a value in JSON |
封装JSON中的值 |
接口间通信
插件接口(Interface)的作用,就是提供一个与其他系统交互的方法。其他系统无需(也无法)了解内部的具体细节,只能通过对外提供的接口来与进行通信。
纯虚函数(包括槽)很容易理解,那么信号呢?
的确,这个话题非常有意思。。。通常,我们会定义一些纯虚的槽函数,但关于纯虚信号这个话题讨论的比较少!那么,信号可不可以是纯虚的呢?
一些尝试
关于信号和纯虚,我们知道:
- 信号永远不会有实现(也就是说,在
.h
中定义了信号,在 .cpp
中不需要实现)
- 声明一个纯虚函数的主要目的,是强制继承的类提供一个实现。
信号没有实现,如果将其声明为纯虚的,需要继承的类来提供一个实现,这与“信号没有实现”直接冲突。就好比让一个人同时出现在两个地方,这是不可能的。因此,似乎声明一个纯虚信号是一个错误。
在编写完一个接口时,为了能使用 Qt 的信号槽特性,很多人可能会写出类似下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #ifndef PLUGININTERFACE_H #define PLUGININTERFACE_H #include <QObject> #include <QStringList> #include <QtWidgets/qwidget.h> class PluginInterface : public QObject { Q_OBJECT public: virtual ~PluginInterface() {}
public: virtual void setInitData(QStringList &strlist) = 0; virtual void getResultData(QStringList &strlist) = 0;
signals: virtual void information(const QString&) = 0; }; #define PluginInterface_iid "QtPluginsTest.QtPluginsManager.PluginInterface"
Q_DECLARE_INTERFACE(PluginInterface, PluginInterface_iid)
#endif
|
很遗憾,Qt 发出了警告:
warning: Signals cannot be declared virtual
那么,如何解决这个问题呢?
解决方案
下面,列出三种解决方案:
- 派生自
QObject
,信号不使用 virtual
- 将“信号”定义为一个纯虚函数
- 在接口中定义信号槽的连接方式
派生自 QObject
,信号不使用 virtual
不让用 virtual
,好吧,那就不用了,这算是最简单的解决方式!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #ifndef PLUGININTERFACE_H #define PLUGININTERFACE_H #include <QObject> #include <QStringList> #include <QtWidgets/qwidget.h> class PluginInterface : public QObject { Q_OBJECT public: virtual ~PluginInterface() {}
public: virtual void setInitData(QStringList &strlist) = 0; virtual void getResultData(QStringList &strlist) = 0;
signals: void information(const QString&) = 0; }; #define PluginInterface_iid "QtPluginsTest.QtPluginsManager.PluginInterface"
Q_DECLARE_INTERFACE(PluginInterface, PluginInterface_iid)
#endif
|
具体的实现也与信号无关了,只需要实现其他接口即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #ifndef GENERICPLUGIN1_H #define GENERICPLUGIN1_H #include <QObject> #include <QtDebug> #include <../pluginmanager/plugininterface.h> class GenericPlugin1 : public PluginInterface { Q_OBJECT public: GenericPlugin1() {} void setInitData(QStringList &strlist) Q_DECL_OVERRIDE { qDebug() << "setInitDat..."<<strlist; } void getResultData(QStringList &strlist) Q_DECL_OVERRIDE { qDebug() << "getResultData..."<<strlist; } }; #endif
|
但是这种方式并不算理想,因为一般来说,接口是一个简单的 C++ 类,不需要派生自 QObject
。
将“信号”定义为一个纯虚函数
将“信号”定义为一个纯虚函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #ifndef PLUGININTERFACE_H #define PLUGININTERFACE_H #include <QStringList> #include <QtWidgets/qwidget.h> class PluginInterface { public: virtual ~PluginInterface() {}
public: virtual void setInitData(QStringList &strlist) = 0; virtual void getResultData(QStringList &strlist) = 0;
virtual information(const QString&) = 0; }; #define PluginInterface_iid "QtPluginsTest.QtPluginsManager.PluginInterface"
Q_DECLARE_INTERFACE(PluginInterface, PluginInterface_iid)
#endif
|
注意: 对于 PluginInterface
来说,information()
只是一个纯虚函数,并不是一个“信号”(但可以当做信号来用),因为 PluginInterface
不是 QObject
。
由于需要信号支持,所以具体实现需要派生自 QObject
。此外,还需要将 information()
定义为 signals
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #ifndef GENERICPLUGIN1_H #define GENERICPLUGIN1_H #include <QObject> #include <QtPlugin> #include <QtDebug> #include <../pluginmanager/plugininterface.h> class GenericPlugin1 : public QObject, public PluginInterface { Q_OBJECT public: GenericPlugin1() {} void setInitData(QStringList &strlist) Q_DECL_OVERRIDE { qDebug() << "setInitDat..."<<strlist; } void getResultData(QStringList &strlist) Q_DECL_OVERRIDE { qDebug() << "getResultData..."<<strlist; } signals: void information(const QString&) Q_DECL_OVERRIDE; };
#endif
|
这时,information()
的具体的实现在内部是由 moc
来完成的。
为了测试是否有效,再定义一个类 - monitor.h
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #ifndef MONITOR_H #define MONITOR_H #include <QObject> #include <QtDebug> #include <../pluginmanager/plugininterface.h> class Monitor : public QObject { Q_OBJECT private slots: void onInformation(const QString& info) { qDebug() <<"Information..."<< info; } public: void monitorInformation(PluginInterface *plugin) { QObject *Object = dynamic_cast<QObject *>(plugin); if (Object) { connect(Object, SIGNAL(information(const QString&)), this, SLOT(onInformation(const QString&))); } else { qWarning() << "cannot monitor Information"; } } }; #endif
|
注意: 连接信号时,需要将其转换为 QObject
。
在 main.cpp
中进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <QCoreApplication> #include "genericplugin1.h" #include "monitor.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); GenericPlugin1 plugin; Monitor monitor; monitor.monitorInformation(&plugin); emit plugin.information("ddddddddd"); return a.exec(); }
|
在接口中定义信号槽的连接方式
除了上述方式之外,还可以在接口中定义信号槽的连接方式。
首先,定义一个连接信号槽的接口 - connectToInformation()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #ifndef PLUGININTERFACE_H #define PLUGININTERFACE_H #include <QStringList> #include <QtWidgets/qwidget.h> class PluginInterface { public: virtual ~PluginInterface() {}
public: virtual void setInitData(QStringList &strlist) = 0; virtual void getResultData(QStringList &strlist) = 0;
virtual bool connectToInformation(QObject *receiver, const char* pszSlot, bool isConnect = true) const = 0; }; #define PluginInterface_iid "QtPluginsTest.QtPluginsManager.PluginInterface"
Q_DECLARE_INTERFACE(PluginInterface, PluginInterface_iid)
#endif
|
然后,在派生类中实现信号槽的具体连接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #ifndef GENERICPLUGIN1_H #define GENERICPLUGIN1_H
#include <QObject> #include <QtPlugin> #include <../pluginmanager/plugininterface.h>
class GenericPlugin1 : public QObject, public PluginInterface { Q_OBJECT Q_PLUGIN_METADATA(IID PluginInterface_iid FILE "programmer.json") Q_INTERFACES(PluginInterface) public: GenericPlugin1() {}
void setInitData(QStringList &strlist) Q_DECL_OVERRIDE { qDebug() << "setInitDat..." << strlist; emit information(strlist.at(0)); }
void getResultData(QStringList &strlist) Q_DECL_OVERRIDE { qDebug() << "getResultData..." << strlist; }
bool connectToInformation(QObject *receiver, const char *pszSlot, bool isConnect) const Q_DECL_OVERRIDE { if(isConnect) return connect(this, SIGNAL(information(const QString&)), receiver, pszSlot); else return disconnect(this, SIGNAL(information(const QString&)), receiver, pszSlot); }
signals: void information(const QString&);
};
#endif
|
这样以来,连接方式就变得简单多了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #ifndef MONITOR_H #define MONITOR_H
#include <QObject> #include <QtDebug> #include <../pluginmanager/plugininterface.h>
class Monitor : public QObject { Q_OBJECT
public slots: void onInformation(const QString& info) { qDebug() << "Information..." << info; }
public: void monitorInformation(PluginInterface *plugin) { bool isConnect = plugin->connectToInformation(this, SLOT(const onInformation()), true); if (isConnect) { qWarning() << "connectToInformation"; } else { qWarning() << "cannot monitor Information"; } } };
#endif
|
除了信号槽之外,Qt 还可以通过事件机制(sendEvent()
和 postEvent()
)来实现接口之间的通信。正如上所示,只要通过接口来获得 QObject
即可。
插件管理器
Qt 本身提供了插件相关的技术,但并没有提供一个通用的插件框架!倘若要开发一个较大的 GUI 应用程序,并希望使其可扩展,那么拥有这样一个插件框架无疑会带来很大的好处。
插件系统构成
插件系统,可以分为三部分:
- 主系统
通过插件管理器加载插件,并创建插件对象。一旦插件对象被创建,主系统就会获得相应的指针/引用,它可以像任何其他对象一样使用。
- 插件管理器
用于管理插件的生命周期,并将其暴露给主系统。它负责查找并加载插件,初始化它们,并且能够进行卸载。它还应该让主系统迭代加载的插件或注册的插件对象。
- 插件
插件本身应符合插件管理器协议,并提供符合主系统期望的对象。
实际上,很少能看到这样一个相对独立的分离,插件管理器通常与主系统紧密耦合,因为插件管理器需要最终提供(定制)某些类型的插件对象的实例。
程序流
框架的基本程序流,如下所示:

插件管理器
上面提到,插件管理器有一个职责 - 加载插件。那么,是不是所有的插件都需要加载呢?当然不是!只有符合我们约定的插件,才会被认为是标准的、有效的插件,外来插件一律认定为无效。
创建空项目pluginmanager
.pro 文件为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| QT -= gui
TARGET = qtpluginmanager TEMPLATE = lib
DEFINES += QTPLUGINSMANAGER_LIBRARY
win32 { CONFIG(debug, debug|release) { DESTDIR = ../Main/debug } else { DESTDIR = ../Main/release } }
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \ qtpluginmanager.cpp \ qtpluginsmanagerprivate.cpp
HEADERS += plugininterface.h \ pluginmetadata.h \ qtpluginmanager.h \ qtpluginsmanager_global.h \ qtpluginsmanagerprivate.h
|
为了解决这个问题,可以为插件设定一个“标识(Interface)” - PluginInterface.h
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #ifndef PLUGININTERFACE_H #define PLUGININTERFACE_H #include "pluginmetadata.h" class PluginInterface { public: virtual ~PluginInterface() {}
public:
virtual void setInitData(QMap<QString,QVariant> &data) = 0;
virtual QMap<QString,QVariant>& getResultData() = 0;
virtual void recMsgFromManager(PluginMetaData &msg) = 0;
virtual bool connectTosendMsgToManager(QObject *receiver, const char* pszSlot, bool isConnect = true) const = 0; }; #define PluginInterface_iid "QtPlugins.QtPluginsManager.PluginInterface"
Q_DECLARE_INTERFACE(PluginInterface, PluginInterface_iid)
#endif
|
后期实现的所有插件,都必须继承自 PluginInterface
,这样才会被认定是自己的插件,以防外部插件注入。
**注意:**使用 Q_DECLARE_INTERFACE
宏,将 PluginInterface
接口与标识符一起公开。
插件的基本约束有了,插件的具体实现插件管理器并不关心,它所要做的工作是加载插件、卸载插件、检测插件的依赖、以及扫描插件的元数据(Json 文件中的内容)。。。为了便于操作,将其实现为一个单例。
qtpluginsmanager_global.h
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12
| #ifndef QTPLUGINSMANAGER_GLOBAL_H #define QTPLUGINSMANAGER_GLOBAL_H
#include <QtCore/qglobal.h>
#if defined(QTPLUGINSMANAGER_LIBRARY) # define QTPLUGINSMANAGERSHARED_EXPORT Q_DECL_EXPORT #else # define QTPLUGINSMANAGERSHARED_EXPORT Q_DECL_IMPORT #endif
#endif
|
qtpluginmanager.h
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| #ifndef QTPLUGINSMANAGER_H #define QTPLUGINSMANAGER_H
#include "qtpluginsmanager_global.h" #include "pluginmetadata.h" #include <QObject> #include <QPluginLoader> #include <QVariant>
class QtPluginsManagerPrivate;
class QTPLUGINSMANAGERSHARED_EXPORT QtPluginsManager : public QObject { Q_OBJECT public:
QtPluginsManager(); ~QtPluginsManager();
static QtPluginsManager *instance(){ if(m_instance==nullptr) m_instance=new QtPluginsManager(); return m_instance; }
void loadAllPlugins();
void scanMetaData(const QString &filepath); void loadPlugin(const QString &filepath); void unloadAllPlugins(); void unloadPlugin(const QString &filepath); QList<QPluginLoader *> allPlugins(); QPluginLoader* getPlugin(const QString &name); QList<QVariant> allPluginsName(); QVariant getPluginName(QPluginLoader *loader);
private: static QtPluginsManager *m_instance; QtPluginsManagerPrivate *d;
private slots: void recMsgFromManager(PluginMetaData&);
};
#endif
|
可以看到,插件管理器中有一个 d
指针,它包含了插件元数据的哈希表。此外,由于其拥有所有插件的元数据,所以还为其赋予了另外一个职能 - 检测插件的依赖关系:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #ifndef QTPLUGINSMANAGERPRIVATE_H #define QTPLUGINSMANAGERPRIVATE_H
#include <QHash> #include <QVariant> #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; };
#endif
|
注意: 这里的 check()
是一个递归调用,因为很有可能存在“插件A”依赖于“插件B”,而“插件B”又依赖于“插件C”的连续依赖情况。
QtPluginsManagerPrivate
中的哈希表在初始化插件管理器时被填充:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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()
负责:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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; }
|
一旦所有元数据被扫描,便可以检查是否能够加载插件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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->connectTosendMsgToManager(this, SLOT(recMsgFromManager(PluginMetaData&)), true); } else { delete loader; loader = nullptr; } } }
|
注意: 这里用到了前面提到的标识 - PluginInterface
,只有 qobject_cast
转换成功,才会加载到主系统中,这可以算作是真正意义上的第一道防线。
实际上,在内部检查是通过调用 QtPluginManagerPrivate::check()
递归地查询依赖元数据来完成的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| 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; }
|
插件卸载的过程正好相反:
1 2 3 4 5
| void QtPluginsManager::unloadAllPlugins() { for(QString filepath : d->m_loaders.keys()) unloadPlugin(filepath); }
|
而具体的卸载由 unloadPlugin()
来完成:
1 2 3 4 5 6 7 8 9 10 11
| 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; } }
|
万事俱备,然后返回所有的插件,以便主系统访问:
1 2 3 4
| QList<QPluginLoader *> QtPluginsManager::allPlugins() { return d->m_loaders.values(); }
|
槽recMsgFromManager:
1 2 3 4 5 6 7 8 9 10 11 12 13
| void QtPluginsManager::recMsgFromManager(PluginMetaData &msg) { qDebug() <<"QtPluginsManager::recMsgFromManager..."<< msg.dest; auto loader = getPlugin(msg.dest); if(loader) { auto plugin = qobject_cast<PluginInterface*>(loader->instance());; if(plugin) { plugin->recMsgFromManager(msg); } } }
|
这样,整个插件管理的机制已经建立起来了,剩下的事基本就比较简单了!插件的编写、插件之间的交互。
编写插件
插件1:GenericPlugin1,.pro、.h文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| TEMPLATE = lib CONFIG += plugin TARGET = GenericPlugin1 win32 { CONFIG(debug, debug|release) { DESTDIR = ../Main/debug/plugins } else { DESTDIR = ../Main/release/plugins } }
HEADERS += \ genericplugin1.h
DISTFILES += \ programmer.json
msvc { QMAKE_CXXFLAGS += /utf-8 }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #ifndef GENERICPLUGIN1_H #define GENERICPLUGIN1_H
#include <QObject> #include <QtPlugin> #include "../qtpluginmanager/pluginmetadata.h" #include "../qtpluginmanager/plugininterface.h"
class GenericPlugin1 : public QObject, public PluginInterface { Q_OBJECT Q_PLUGIN_METADATA(IID PluginInterface_iid FILE "programmer.json") Q_INTERFACES(PluginInterface) public: GenericPlugin1() {} ~GenericPlugin1() {}
void setInitData(QMap<QString,QVariant> &data) Q_DECL_OVERRIDE { qDebug() << "GenericPlugin1 setInitDat..." << data; map=&data; #测试插件1发送信息给插件2 PluginMetaData metadata; metadata.from = "GenericPlugin1"; metadata.dest = "GenericPlugin2"; metadata.type = 1; emit sendMsgToManager(metadata); }
QMap<QString,QVariant>& getResultData() Q_DECL_OVERRIDE { qDebug() << "GenericPlugin1 getResultData..."; return *map; }
void recMsgFromManager(PluginMetaData &msg) Q_DECL_OVERRIDE { qDebug() << "GenericPlugin1 recMsgFromManager..." << msg.from; }
bool connectTosendMsgToManager(QObject *receiver, const char *pszSlot, bool isConnect) const Q_DECL_OVERRIDE { if(isConnect) return connect(this, SIGNAL(sendMsgToManager(PluginMetaData&)), receiver, pszSlot); else return disconnect(this, SIGNAL(sendMsgToManager(PluginMetaData&)), receiver, pszSlot); }
private: QMap<QString,QVariant> *map;
signals: void sendMsgToManager(PluginMetaData&);
};
#endif
|
插件2:GenericPlugin1,.pro、.h文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| TEMPLATE = lib CONFIG += plugin TARGET = GenericPlugin2 win32 { CONFIG(debug, debug|release) { DESTDIR = ../Main/debug/plugins } else { DESTDIR = ../Main/release/plugins } }
HEADERS += \ genericplugin2.h
DISTFILES += \ programmer.json
msvc { QMAKE_CXXFLAGS += /utf-8 }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #ifndef GENERICPLUGIN2_H #define GENERICPLUGIN2_H
#include <QObject> #include <QtPlugin> #include "../qtpluginmanager/pluginmetadata.h" #include "../qtpluginmanager/plugininterface.h"
class GenericPlugin2 : public QObject, public PluginInterface { Q_OBJECT Q_PLUGIN_METADATA(IID PluginInterface_iid FILE "programmer.json") Q_INTERFACES(PluginInterface) public: GenericPlugin2() {} ~GenericPlugin2() {}
void setInitData(QMap<QString,QVariant> &data) Q_DECL_OVERRIDE { qDebug() << "GenericPlugin2 setInitDat..." << data; map=&data; }
QMap<QString,QVariant>& getResultData() Q_DECL_OVERRIDE { qDebug() << "GenericPlugin2 getResultData..."; return *map; }
void recMsgFromManager(PluginMetaData &msg) Q_DECL_OVERRIDE { qDebug() << "GenericPlugin2 recMsgFromManager..." << msg.from; }
bool connectTosendMsgToManager(QObject *receiver, const char *pszSlot, bool isConnect) const Q_DECL_OVERRIDE { if(isConnect) return connect(this, SIGNAL(sendMsgToManager(PluginMetaData&)), receiver, pszSlot); else return disconnect(this, SIGNAL(sendMsgToManager(PluginMetaData&)), receiver, pszSlot); }
private: QMap<QString,QVariant> *map;
signals: void sendMsgToManager(PluginMetaData&);
};
#endif
|
测试插件通讯
测试插件1发送信息给插件2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| QtPluginsManager::instance()->loadAllPlugins(); auto plugins=QtPluginsManager::instance()->allPlugins(); foreach(auto plugin,plugins) { PluginInterface *app = qobject_cast<PluginInterface*>(plugin->instance()); if (app) { QMap<QString,QVariant> data; data.insert("0001",10000); app->setInitData(data); } }
QtPluginsManager::instance()->unloadAllPlugins();
|
打印出如下信息,说明成功。
1 2 3 4
| GenericPlugin2 setInitDat... QMap(("0001", QVariant(int, 10000))) GenericPlugin1 setInitDat... QMap(("0001", QVariant(int, 10000))) QtPluginsManager::recMsgFromManager... "GenericPlugin2" GenericPlugin2 recMsgFromManager... "GenericPlugin1"
|