Qt學習--Qt Plugin建立及呼叫2(外掛管理器)
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();
}
這樣,整個外掛管理的機制已經建立起來了,萬里長征第一步。。。那剩下的事基本就比較簡單了!外掛的編寫、外掛之間的互動。。。
相關文章
- QT外掛學習QT
- Qt自定義外掛plugin的開發和呼叫QTPlugin
- Qt DLL總結【二】-建立及呼叫QT的 DLLQT
- Qt入門(11)——Qt外掛QT
- Qt學習2QT
- Qt學習之路2QT
- qt呼叫js,js呼叫qtQTJS
- cmake構建Qt外掛QT
- VS中呼叫QT出現This application failed to start because it could not find or load the Qt platform pluginQTAPPAIPlatformPlugin
- 錯誤 qt.qpa.plugin: Could not find the Qt platform plugin “windows“ in ““ 的解決方法QTPluginPlatformWindows
- 排查qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.QTPluginPlatform
- Qt Creator 原始碼學習筆記04,多外掛實現原理分析QT原始碼筆記
- Qt入門(13)——Qt的呼叫退出QT
- Qt學習之XMLQTXML
- 【轉】qt-vs-addin:Qt4和Qt5之VS外掛如何共存與使用QT
- qt程式建立及模板程式碼分析QT
- Qt 事件機制 學習QT事件
- QT學習筆記(三)——QT中的座標系統及視窗位置,大小配置QT筆記
- 【Qt】Qt再學習(二):Chart Themes Example(常用圖表)QT
- Qt Creater 2QT
- QT學習筆記4(動畫)QT筆記動畫
- QT5學習 QFileSystemModelQT
- QT學習記錄總結QT
- QT 學習錯誤總結QT
- Qt QChart 建立圖表QT
- FTP publisher plugin外掛FTPPlugin
- QT學習筆記1(安裝、建立和訊號與槽)QT筆記
- Notepad++無法升級外掛管理器Plugin managerPlugin
- Qt學習- (掃雷專案初學)QT
- Qt開發Activex筆記(二):Qt呼叫Qt開發的Activex控制元件QT筆記控制元件
- qt-5.6.0 移植之qt檔案系統的建立QT
- QT5學習 QStringListModelQT
- Qt學習之路(50): QString薦QT
- [Android] Qt安卓教程(2):移植Qt到安卓AndroidQT安卓
- Flutter 外掛的建立及使用Flutter
- Grafana外掛Plugin中文漢化GrafanaPlugin
- 什麼是qt,QT Creator, QT SDK, QT DesignerQT
- QT學習 實時顯示時間QT