Qt5筆記之Qt5外掛的生成與載入及json檔案的讀取
一、前言
1. Qt Plugin按照應用場景分兩種型別:
(1)The High-Level API:用於擴充套件Qt本身的功能,需放在Qt安裝目錄下的指定目錄裡;
Qt Plugin按照型別又可分為兩種:動態外掛(dll)和靜態外掛(lib);
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