怎樣在工商局網站做公示網絡營銷案例及分析
簡介:
插件(Plug-in,又稱addin、add-in、addon或add-on,又譯外掛)是一種遵循一定規(guī)范的應用程序接口編寫出來的程序。
Qt 提供了2種APIs來創(chuàng)建插件:
一種高級API,用于為Qt本身編寫插件:自定義數據庫驅動程序,圖像格式,文本編解碼器,自定義樣式等。
一種用于擴展Qt應用程序的低級API。也就是說擴展我們自己使用Qt編寫的程序。這要求應用程序使用 QPluginLoader 檢測和加載插件。在這種情況下,插件可以提供任意功能,不限于數據庫驅動程序、圖像格式、文本編解碼器、樣式以及擴展Qt功能的其他類型的插件。本文主要通過示例來展示第二種方式創(chuàng)建插件。
原理:
重載了虛函數的dll,這跟抽象工廠類類似,這便是插件的原理。qt的插件可以說是一種動態(tài)庫
Qt插件特點:
核心技術:QPluginLoader
Qt插件存儲在共享庫(DLL)中(即以動態(tài)庫的方式存在),與使用QLibrary訪問的共享庫相比,它具有以下優(yōu)勢:
面向Interface編程,內部封裝,模塊和整體流程開發(fā)分離,提高開發(fā)效率。
QPluginLoader 檢查插件是否與應用程序相同版本的 Qt 鏈接。
QPluginLoader 提供對根組件對象 (instance()) 的直接訪問,而不是強制您手動解析 C 函數
插件
插件主要面向接口編程,無需訪問.lib文件,熱插拔、利于團隊開發(fā)。即使在程序運行時.dll不存在,也可以正常啟動,只是相應插件功能無法正常使用而已;
動態(tài)庫
動態(tài)庫需要訪問.lib文件,而且在程序運行時必須保證.lib存在,否則無法正常啟動
開發(fā)測試環(huán)境:QT5.15.2 + MSVC 2019
在容器端:
- 定義一組接口(只有純虛函數的類)。
- 在QT_BEGIN_NAMESPACE和QT_END_NAMESPACE之間使用Q_DECLARE_INTERFACE()宏告訴Qt的元對象系統(tǒng)有關接口的信息。
- 在程序中使用 QPluginLoader加載插件。
- 使用qobject_cast()測試插件是否實現給定的接口。
Q_DECLARE_INTERFACE
Q_DECLARE_INTERFACE(類名,標識符)
此宏用于把標識符與類名接口關聯起來。這個標識符是唯一的,這個宏通常在被放到一個類被定后的位置。
這個是Qt實現插件必須要有的一個宏, 把標識符與類名接口關聯起來。
在插件端:
聲明一個插件類,該類繼承自QObject和插件要提供的接口類。
使用Q_INTERFACES()宏告訴Qt的元對象系統(tǒng)有關接口的信息。
使用Q_PLUGIN_METADATA()宏導出插件。
Q_PLUGIN_METADATA
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.SubPlugin" FILE "SubPlugin.json")
這個宏第一次參數定義了一個uuid,保證唯一即可,第二個json是必須要有的,當無法找到指定的文件時,moc 會出現錯誤,即使是空的文件也行
SubPlugin.json文件存放在源代碼目錄下,可以記錄一下插件的信息(如插件名稱、插件版本、插件依賴庫),如下
{
????"name": "testSubPlugin",
????"version": "1.0",
????"dependencies": []
}
這個宏被用于聲明元數據,這個元數據是被實例化插件的一部分。
這個宏需要通過對象聲明被實例化接口的IID,并且要引用包含元數據內容的文件。
需要獲取這些元數據(即上面的json數據),可在在通過下面的辦法。
????????QPluginLoader *loader = new QPluginLoader(this);
????????loader->setFileName(plugin.absoluteFilePath());
????????qDebug()<<loader->metaData().keys();
????????QJsonObject json = loader->metaData().value("MetaData").toObject();
????????QVariant var = json.value("name").toVariant();
Q_INTERFACES
Q_INTERFACES(AppInterface) ??//聲明這個插件實現是基于哪個插件接口的,使qobject_cast()能正確進行QObject*到接口指針的轉換
QPluginLoader?類
~QPluginLoader()
銷毀 QPluginLoader 對象。
除非顯式調用 unload(),否則插件會一直保留在內存中,直到應用程序終止。
Load
加載插件,如果插件加載成功則返回true; 否則返回false。
unload
卸載插件,如果插件可以卸載則返回true,否則返回false。
這在應用程序終止時自動發(fā)生,因此通常不需要調用此函數。
如果 QPluginLoader 的其他實例正在使用相同的插件,則調用將失敗,并且只有在每個實例都調用了 unload() 時才會發(fā)生卸載。
不要嘗試刪除根組件。 而是依靠 unload() 會在需要時自動刪除它
Instance
返回插件的根組件對象,組件對象是一個 QObject。 使用 qobject_cast() 可將其轉成所需的對象。如果根組件對象被銷毀,則調用此函數會創(chuàng)建一個新實例。
該函數返回的根組件在 QPluginLoader 銷毀時不會被刪除。 如果要確保刪除根組件,則應在不再需要訪問核心組件時立即調用 unload()。 當庫最終卸載時,根組件將自動刪除。
metaData
返回此插件的元數據。 元數據是在編譯插件時使用 Q_PLUGIN_METADATA() 宏以 json 格式指定的數據。
例子
首先創(chuàng)建一個Qt Subdirs Project工程PluginDemo,并添加一個app應用程序端。并在app.pro添加輸出目錄DESTDIR = $$PWD/../bin
- 然后添加一個appinterface.h的頭文件,該類作為插件接口類。
#ifndef APPINTERFACE_H#define APPINTERFACE_H#include <QList>#include <QAction>class AppInterface{public:virtual ~AppInterface() {}// 插件的名字virtual QString name() const = 0;// 插件返回的QAction列表virtual QList<QAction *> actions() const = 0;};QT_BEGIN_NAMESPACEQ_DECLARE_INTERFACE(AppInterface, "plugindemo_app_interface")QT_END_NAMESPACE#endif // APPINTERFACE_H
- 在工程上右鍵,選擇“新子項目”,選擇Library->C++ Library,類型選擇“shared Library”,qt module 選擇“widgets”.插件名稱為subplugin.
- 修改subplugin.pro
添加CONFIG+=plugin??//目的就是為是該dll作為插件
target.path = $$PWD/../bin/plugins、INSTALL+=target?//把插件dll拷貝到容器的bin目錄下。需構建步驟添加 make install
- 實現接口類SubPlugin.h,注意添加Q_INTERFACES和Q_PLUGIN_METADATA宏
#ifndef SUBPLUGIN_H#define SUBPLUGIN_H#include "subPlugin_global.h"#include "../app/appinterface.h"#include <QObject>class SUBPLUGIN_EXPORT SubPlugin : public QObject, public AppInterface{public:SubPlugin();Q_OBJECTQ_INTERFACES(AppInterface) ??//聲明這個插件實現是基于哪個插件接口的Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.SubPlugin" FILE "SubPlugin.json") ?//使用Q_PLUGIN_METADATA宏導出插件// AppInterface interfacepublic:virtual QString name() const override;virtual QList<QAction *> actions() const override;};#endif // SUBPLUGIN_H#include "subplugin.h"SubPlugin::SubPlugin(){}QString SubPlugin::name() const{return "SubPlugin";}QList<QAction *> SubPlugin::actions() const{QAction *aboutQt = new QAction;aboutQt->setText(tr("About Qt"));QList<QAction *> result;result << aboutQt;return result;}
- 在容器的Dialog.cpp里面添加加載插件的代碼。
#include "dialog.h"#include "ui_dialog.h"#include "appinterface.h"#include <QDebug>#include <QPluginLoader>#include <QFileInfo>#include <QDir>#include <QPushButton>Dialog::Dialog(QWidget *parent): QDialog(parent), ui(new Ui::Dialog){loadPlugins();ui->setupUi(this);}Dialog::~Dialog(){delete ui;}void Dialog::loadPlugins(){QString pluginPath = QCoreApplication::applicationDirPath() + "/plugins";QStringList filters;#if defined(Q_OS_LINUX)filters << "*.so";#elif defined(Q_OS_WIN)filters << "*.dll";#endifQFileInfoList plugins = QDir(pluginPath).entryInfoList(filters,QDir::Files | QDir::NoSymLinks);for (int i = 0; i < plugins.count(); ++i) {QFileInfo plugin = plugins.at(i);QPluginLoader *loader = new QPluginLoader(this);loader->setFileName(plugin.absoluteFilePath());qDebug()<<loader->metaData().keys();QJsonObject json = loader->metaData().value("MetaData").toObject();QVariant var = json.value("name").toVariant();bool result = loader->load();qDebug() << QString("load plguin %1 result: ").arg(plugin.absoluteFilePath()) << result;if (result) {QObject *pluginInstance = loader->instance();AppInterface *appInterface = qobject_cast<AppInterface *>(pluginInstance);if (appInterface != nullptr) {qDebug() << "add actions from plugin:" << appInterface->name();QList<QAction *> actions = appInterface->actions();for (int k = 0; k < actions.count(); ++k) {QAction *ac = actions.at(k);QString strtext = ac->text();}}} else {qDebug() << loader->errorString();}}}
插件管理
自定義pluginmanager類,封裝成dll
管理所有插件,包括加載、卸載等。并暴露方法給主程序。
插件管理器代碼
插件接口
#ifndef PLUGININTERFACE_H#define PLUGININTERFACE_H#include <QtWidgets/qwidget.h>#include <QString>class PluginInterface{public:virtual ~PluginInterface() {}// 插件返回的QAction列表virtual QString datawrite() = 0;};Q_DECLARE_INTERFACE(PluginInterface, "PluginManager.PluginInterface")#endif // PLUGININTERFACE_H
插件管理器
#ifndef PLUGINMANAGER_H#define PLUGINMANAGER_H#include "PluginManager_global.h"#include <QObject>#include <QPluginLoader>class PLUGINMANAGER_EXPORT PluginManager:public QObject{Q_OBJECTprivate:PluginManager();public:static PluginManager *getinstance();//加載所有插件void loadAllPlugins();//卸載所有插件void unloadAllPlugins();QString datawrite();private:static PluginManager *m_instance;QHash<QString, QPluginLoader *>m_loaders; //插件路徑--QPluginLoader實例};#endif // PLUGINMANAGER_H#include "pluginmanager.h"#include <QDebug>#include <QPluginLoader>#include <QFileInfo>#include <QDir>#include <QCoreApplication>#include"PluginInterface.h"PluginManager* PluginManager::m_instance=nullptr;PluginManager::PluginManager(){}PluginManager *PluginManager::getinstance(){if(m_instance==nullptr)m_instance= new PluginManager();return m_instance;}void PluginManager::loadAllPlugins(){QString pluginPath = QCoreApplication::applicationDirPath() + "/plugins";QStringList filters;#if defined(Q_OS_LINUX)filters << "*.so";#elif defined(Q_OS_WIN)filters << "*.dll";#endifQFileInfoList plugins = QDir(pluginPath).entryInfoList(filters,QDir::Files | QDir::NoSymLinks);for (int i = 0; i < plugins.count(); ++i){QFileInfo plugin = plugins.at(i);QPluginLoader *loader = new QPluginLoader(this);loader->setFileName(plugin.absoluteFilePath());qDebug()<<loader->metaData().keys();QJsonObject json = loader->metaData().value("MetaData").toObject();QVariant var = json.value("name").toVariant();bool result = loader->load();qDebug() << QString("load plguin %1 result: ").arg(plugin.absoluteFilePath()) << result;if (result){m_loaders.insert(plugin.absoluteFilePath(), loader);QObject *pluginInstance = loader->instance();PluginInterface *appInterface = qobject_cast<PluginInterface *>(pluginInstance);if (appInterface != nullptr){}}else{qDebug() << loader->errorString();}}}void PluginManager::unloadAllPlugins(){for(auto filepath : m_loaders.keys()){QPluginLoader *loader = m_loaders.value(filepath);//卸載插件,并從內部數據結構中移除if(loader->unload()){m_loaders.remove(filepath);delete loader;loader = nullptr;}}}QString PluginManager::datawrite(){QString str;for(auto filepath : m_loaders.keys()){QPluginLoader *loader = m_loaders.value(filepath);PluginInterface *Plugin = dynamic_cast<PluginInterface*>(loader->instance());if(Plugin)str = Plugin->datawrite();}return str;}
插件代碼
#ifndef LOGPLUGIN_H#define LOGPLUGIN_H#include "logPlugin_global.h"#include "../PluginManager/PluginInterface.h"#include <QObject>class LOGPLUGIN_EXPORT LogPlugin: public QObject,public PluginInterface{Q_OBJECTQ_INTERFACES(PluginInterface) ??//聲明這個插件實現是基于哪個插件接口的Q_PLUGIN_METADATA(IID "org.qt-LogPlugin" FILE "LogPlugin.json") ?//使用Q_PLUGIN_METADATA宏導出插件public:LogPlugin();virtual QString datawrite() override;};#endif // LOGPLUGIN_H#ifndef DBPLUGIN_H#define DBPLUGIN_H#include "DbPlugin_global.h"#include "../PluginManager/PluginInterface.h"#include <QObject>class DBPLUGIN_EXPORT DbPlugin : public QObject,public PluginInterface{public: ??Q_OBJECTQ_INTERFACES(PluginInterface) ??//聲明這個插件實現是基于哪個插件接口的Q_PLUGIN_METADATA(IID "org.qt-DbPlugin" FILE "DbPlugin.json") ?//使用Q_PLUGIN_METADATA宏導出插件public:DbPlugin();virtual QString datawrite() override;};#endif // DBPLUGIN_H
App代碼
#include "dialog.h"#include "ui_dialog.h"#include <QDebug>#include <QPluginLoader>#include <QFileInfo>#include <QDir>#include <QPushButton>#include "../PluginManager/pluginmanager.h"Dialog::Dialog(QWidget *parent): QDialog(parent), ui(new Ui::Dialog){ui->setupUi(this);}Dialog::~Dialog(){delete ui;}void Dialog::on_pushButton_clicked(){PluginManager::getinstance()->loadAllPlugins();PluginManager::getinstance()->datawrite();PluginManager::getinstance()->unloadAllPlugins();}
插件通信
插件的通信通過插件管理器來管理,插件管理器轉發(fā)插件的消息
- 在插件管理器的接口頭文件增加一個插件間通信的結構體,增加插件發(fā)送消息和接收消息的純虛函數。
Q_DECLARE_METATYPE(Type)
作用:向Qt元系統(tǒng)注冊一些非基本類型
這個宏能使Qt元系統(tǒng)的QMetaType知道Type類型,但前提是Type類型必須提供一個公有的默認構造函數、公有的默認拷貝構造函數和公有的默認析構函數。
- 在插件重寫插件管理器聲明的接收虛函數,把插件管理器聲明的發(fā)送虛函數聲明為信號。
- 在插件管理器的加載插件處加上connect函數,是信號槽關聯起來。其中把插件發(fā)送信號關聯插件管理器的接收槽函數。
同時讓插件管理器的接收槽函數轉發(fā)給對應的插件
- 在app程序中調用調用插件的信號從而發(fā)送消息
完整代碼參見:
鏈接:https://pan.baidu.com/s/1fWK_oSYQTRNip4FLnTOLwg
提取碼:qc0t