Qt5笔记之Qt5插件的生成与加载及json文件的读取

时间:2023-02-01 14:12:30

一、前言

1. Qt Plugin按照应用场景分两种类型:

(1)The High-Level API:用于扩展Qt本身的功能,需放在Qt安装目录下的指定目录里;

(2)The Lower-Level API:用于扩展Qt应用程序的功能;

Qt Plugin按照类型又可分为两种:动态插件(dll)和静态插件(lib);

以下说的均为The Lower-Level API的动态插件。

2. Qt5不再使用Q_EXPORT_PLUGIN2宏,可以在代码中跳转过去看,会发现这个宏已经作废了,在Qt5中,导出plugin使用Q_PLUGIN_METADATA宏,在Qt助手中搜“How to Create Qt Plugins”可以看到相关说明,还有一个不完整但清晰的demo。

 

二、Qt plugind的生成与加载应用步骤

1. 写一个插件的步骤:

(1)定义一个接口类,使用Q_DECLARE_INTERFACE宏来告诉Qt元系统存在这么个接口;

  注:Qt文档中说用于继承的基类接口必须为接口类(只包含纯虚函数),但是试了一下,同时包含虚函数和纯虚函数的抽象类也是可以作为基类的...大概是因为基类中的虚函数也可以实现用基类的指针调用子类中重写的这个函数,如果是普通函数,应该也可以在接口中存在,毕竟抽象类中也是可以有普通函数的,但是就没什么作用了,因为应用程序使用的是插件中重写的方法,而非基类本身的方法,但是为了尊重文档,这里统一叫做接口类。

(2)声明一个插件类,该插件类继承自QObject和上一步定义的接口类;

(3)使用Q_INTERFACES宏将该接口类告诉Qt元系统;

(4)使用Q_PLUGIN_METADATA宏导出该插件类;

(5)生成dll;

 

2. 在其他应用程序使用插件的步骤:

(1)附加包含目录中添加接口类头文件的路径,不需要添加插件类的头文件,只要include接口类头文件就可以找到两者,这也是插件方便之处,当有新版本的插件时只要把dll扔过去就可以,无需其他改动;

注意:

a. 如果接口类和plugin在同一个工程中,那么直接把该工程生成的dll扔过去就可以了;

b. 如果接口类和plugin在不同的工程中,那么要把这两个工程都编译生成dll扔过去才可以,也就是:接口类所在工程的.h、.lib、.dll都要添一遍,不然加载不了plugin(毕竟plugin继承的Interface都没有,儿子是不会同意没有爸爸的),然后每次更新插件还是只需要扔plugin所在工程的dll;

 

(2)在应用程序中使用QPluginLoader类加载plugin;

(3)用qobject_cast()来判断插件是否实现了接口;

 

三、Qt plugin的生成

  首先是定义接口,尤其要注意,结尾的那个宏一定要有,如下:

//Interface.h
class Interface
{
public:
virtual ~Interface() {}

virtual QStringList filters() const = 0;
virtual QImage filterImage(const QString &filter, const QImage &image,
QWidget *parent) = 0;
};
#define QDesignerCustomWidgetInterface_iid "org.qt-project.Interface"
Q_DECLARE_INTERFACE(Interface, QDesignerCustomWidgetInterface_iid)

  然后定义插件,如下:

//Plugin.h
#include <QObject>
#include <QtPlugin>
#include <QStringList>
#include <QImage>
#include <Interfaces.h>

class Plugin : public QObject, public Interface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Interface" FILE "Interface.json")
Q_INTERFACES(Interface)

public:
QStringList filters() const;
QImage filterImage(const QString &filter, const QImage &image,
QWidget *parent);
};

  Plugin.cpp这里就不写了,就是方法的具体实现,以上就是插件工程部分。

 

四、Qt plugin的应用(加载+使用)

  接下来按照之前所介绍的步骤,编译插件工程生成dll,拷贝到应用工程中,然后要包含Interface的头文件(如果有Interface.cpp,那么还要加上附加库目录和附加依赖项);

  然后通过QPluginLoader类来动态加载插件,也就是xxxx.dll文件,加载比较简单,只要xxxx.dll是一个插件(可以理解为一个有Q_DECLARE_INTERFACE、Q_PLUGIN_METADATA、Q_INTERFACES这三个宏的工程生成的dll就是插件),把它丢到exe所在目录下就可以加载了,如下:

QDir path(qApp->applicationDirPath());
QPluginLoader loader(path.absoluteFilePath("Plugin.dll"));//Plugin.dll是一个插件
if (!loader.load()) {
qDebug() << "It is not a plugin";
return;
}
QObject *obj = loader.instance();
if (obj) {
Interface *plugin = qobject_cast<Interface *>(obj);
}

如果不知道插件名字,并且插件在其他目录下,不在exe所在目录下,可以这样写:

QDir path("../bin");
foreach(QString filename, path.entryList(QDir::Files)) {//遍历path路径下所有文件
QPluginLoader loader(path.absoluteFilePath(filename));
if (!loader.load()) {//不是插件没法load就被过滤出去了
qDebug() << "It is not a plugin";
continue;
}
QObject *obj = loader.instance();
if (obj) {
Interface *plugin = qobject_cast<Interface *>(obj);
}
}

  以上,完成了插件的加载,如果要使用插件中的方法,就直接plugin->method()即可。


注:关于QPluginLoader类的load()方法,作如下说明:

QPluginLoader类有三个加载相关方法:isLoaded()load()unload(),在判断加载成功与否时,注意不要用错方法:

1.函数原型:

load():bool load()

unload():bool unload()

isLoaded():bool isLoaded() const

2.含义:

load():如果插件成功加载则返回true,如果没有成功加载则返回false;

unload():如果插件成功卸载则返回true,如果没有成功卸载则返回false;

isLoaded():如果插件已经被加载了则返回true,如果还没有被加载则返回false;

  从含义里可以看出,调用load()、unload()时是去执行一次插件加载或卸载,然后返回执行结果,而isLoaded()只是查询插件状态是否被加载,并没有执行加载操作,另外,QPluginLoader类instance()方法,会先调用load()再去初始化,所以一般不用load(),直接用instance()去判断,如果返回false,那么可能是加载不成功也可能是初始化不成功,但总之就是加载失败,举几个例子:

QDir path(qApp->applicationDirPath());
QPluginLoader loader(path.absoluteFilePath("Plugin.dll"));//Plugin.dll是一个插件
if (loader.load()) {
qDebug() << "1";
return;
}
if (loader.isLoaded()) {
qDebug() << "2";
return;
}
//输出结果:
//1
//2

QDir path(qApp->applicationDirPath());
QPluginLoader loader(path.absoluteFilePath("Plugin.dll"));//Plugin.dll是一个插件
if (loader.isLoaded()) {
qDebug() << "1";
return;
}
if (loader.load()) {
qDebug() << "2";
return;
}
//输出结果:
//2

QDir path(qApp->applicationDirPath());
QPluginLoader loader(path.absoluteFilePath("Plugin.dll"));//Plugin.dll是一个插件
if (!loader.load()) {
qDebug() << "1";
return;
}
if (!loader.unload()) {
qDebug() << "2";
return;
}
//输出结果:
//1
//2

QDir path(qApp->applicationDirPath());
QPluginLoader loader(path.absoluteFilePath("Plugin.dll"));//Plugin.dll是一个插件
if (!loader.unload()) {
qDebug() << "1";
return;
}
if (!loader.load()) {
qDebug() << "2";
return;
}
//输出结果:
//2


五、json文件的读取

Qt plugin工程在创建时就会自带一个json文件,用于存储元信息和一些配置信息,读取json文件如下:

//Plugin.json
{
"firstName": "Plugin",
"version" : "0.0.1",
"classes" : [
{
"name": "class1",
"version" : "0.0.1",
"icon" : "class1.png"
},
{
"name": "class2",
"version" : "0.0.1",
"icon" : "class2.png"
}
],
"time": [
]
}

//PluginTest.cpp(包括Plugin加载及json读取)
QDir path(qApp->applicationDirPath());
QPluginLoader ploader(path.absoluteFilePath("Plugin.dll"));
QFile *file = new QFile("Plugin.json");
file->open(QIODevice::ReadOnly);
QByteArray b = file->readAll();
QJsonParseError *jsonError = new QJsonParseError;
QJsonDocument doc = QJsonDocument::fromJson(b, jsonError);
if (!doc.isNull() && (jsonError->error == QJsonParseError::NoError)) {
if (doc.isObject())
{
QJsonObject obj = doc.object();//取得最外层这个大对象
//对json数据进行取值
QJsonArray array = obj.value(QString("classes")).toArray();
int arraySize = array.size();
for (auto iter = array.begin(); iter != array.end(); ++iter) {
QJsonValue value = *iter;
QJsonObject object = value.toObject();
if (object.contains(QString("name"))) {
QString str = object.value(QString("name")).toString();
qDebug() << str;
}
QStringList key = object.keys();
}
}
else
{
qDebug() << jsonError->errorString();
}
}

if (!ploader.load()) {
printf("fail to load\n");
return;
}
QObject* t = ploader.instance();
Interface *p = qobject_cast<Interface*>(t);
//输出结果:
//class1
//class2