轉自:https://blog.csdn.net/yizhou2010/article/details/78261643
示例資源:EchoPluginTest
如果沒有積分,可在這里下載:https://files-cdn.cnblogs.com/files/warmlight/EchoPluginTest.rar
概述
插件是一種遵循一定規范的應用程序接口編寫出來的程序,定位於開發實現應用軟件平台不具備的功能的程序。插件與宿主程序之間通過接口聯系,就像硬件插卡一樣,可以被隨時刪除,插入和修改,所以結構很靈活,容易修改,方便軟件的升級和維護。
Qt中的插件
Qt提供了兩種API用於創建插件:
一種是高階API用於擴展Qt本身的功能:如自定義數據庫驅動,圖像格式,文本編碼,自定義樣式等等;
一種低階API用於擴展Qt應用程序。
本文主要是通過低階API來創建Qt插件,並通過靜態、動態兩種方式來調用插件,即通過應用程序預留四則預算接口,通過插件來定義四則運算的簡單例子。
框架自動搭建
我們利用QT Creator把應用程序和插件的運行框架搭建起來,在EchoPluginTest項目項目新建一個名為EchoPluginApp的應用程序,和一個名為EchoPluginLib的插件。
1、首先創建一個子目錄項目,名為EchoPluginTest
2、創建應用程序EchoPluginApp
這里我們選擇的是Qt Widgets Application,並命名為EchoPluginApp
3、創建EchoPluginLib插件
選擇:Library –> C++庫,並在“項目介紹和位置”選擇類型為“Qt Plugin”,名稱為“EchoPluginLib”
在類信息中輸入類名:“EchoPlugin”,基類默認為“QGenericPlugin”
至此,基本框架自動生成完成,我們還需進一步對其進行修改。
應用程序中的插件接口創建
在子項目EchoPluginApp中添加一個echointerface.h文件,在該頭文件中定義插件接口,應用程序正是通過這些接口來實現額外的功能。
1 class EchoInterface 2 { 3 public: 4 virtual ~EchoInterface() {} 5 6 virtual QStringList CalculateType(void) const =0; 7 virtual double Calculate(QString &type,double xvar, double yvar) =0; 8 }; 9 #define EchoInterface_iid "EchoPluginTest.EchoPluginLib.EchoInterface" 10 Q_DECLARE_INTERFACE(EchoInterface, EchoInterface_iid)
EchoInterface 類定義了兩個純虛函數,第一個函數CalculateType() 返回QStringList 用於標識插件中定義的運算類型,之所以采用 QStringList 是因為一個插件中可能存在多個運算類型。第二個函數Calculate(),用於根據傳入的運算類型和運算參數xvar和yvar來返回計算值。
為了能夠在運行時查詢插件是否實現給定的接口,我們必須使用宏Q_DECLARE_INTERFACE(),該宏的第一參數為接口類的名稱,第二個參數是一個字符串,用於唯一標記該接口類。
應用程序中插件接口的使用
在應用程序中,首先我們需要定義loadPlugins() 用於查找插件
1 void MainWindow::loadPlugins() 2 { 3 //調用靜態插件 4 foreach (QObject *plugin, QPluginLoader::staticInstances()) 5 AddToCombo(plugin);
首先通過QPluginLoader::staticInstances()
函數尋找靜態插件,並更新到界面中下拉控件中。
接下來,我們去加載動態插件:
1 //動態調用插件 2 QDir pluginDir(qApp->applicationDirPath()); 3 if(pluginDir.dirName().toLower() == tr("debug") || pluginDir.dirName().toLower() == tr("release") ) 4 { 5 pluginDir.cdUp(); 6 pluginDir.cdUp(); 7 pluginDir.cd("plugins"); 8 } 9 10 //遍歷當前 文件夾下文件 11 foreach (QString filename, pluginDir.entryList(QDir::Files)) 12 { 13 QPluginLoader pluginloader(pluginDir.absoluteFilePath(filename)); 14 QObject *plugin = pluginloader.instance(); 15 if(plugin != 0) 16 AddToCombo(plugin); 17 }
將變量pluginDir 設置到當前應用程序工作目錄對應的Plugins目錄下,並通過函數entryList 遍歷該目錄下所有的文件,對每個文件利用QPluginLoader 去嘗試加載插件。通過QPluginLoader::instance() 函數去識別由插件返回的QObject對象,因為如果動態鏈接庫不是qt插件或者編譯版本不兼容則,函數將返回空指針。
當函數返回有效插件時,我們將其更新到下拉列表中去。
AddToCombo函數
1 void MainWindow::AddToCombo(QObject *pplugin) 2 { 3 EchoInterface *eInterface = qobject_cast<EchoInterface *>(pplugin); 4 if(eInterface != 0) 5 { 6 QStringList typelist = eInterface->CalculateType(); 7 foreach (QString ctype, typelist) 8 { 9 ui->calType->addItem(ctype); 10 PluginItem item; 11 item.text = ctype; 12 item.plugin =pplugin; 13 m_pluginItemList.append(item); 14 } 15 } 16 }
對於每一個插件(不管是動態的,還是靜態的),我們都利用qobject_cast()
函數去檢驗它的接口類,如果為EchoInterface接口則將插件的運算類型添加到下拉列表中去。
下拉列表選擇變化響應槽函數
1 void MainWindow::on_calType_currentIndexChanged(int index) 2 { 3 if(index >=0 && index < m_pluginItemList.size()) 4 { 5 curType = m_pluginItemList.at(index).text; 6 m_EchoInterface = qobject_cast<EchoInterface *>(m_pluginItemList.at(index).plugin); 7 } 8 else 9 { 10 curType = tr(""); 11 m_EchoInterface = 0; 12 } 13 }
計算按鈕響應槽函數
1 void MainWindow::on_calculate_clicked() 2 { 3 if(m_EchoInterface !=0 ) 4 { 5 double value = m_EchoInterface->Calculate(curType, xvar, yvar); 6 ui->resultVal->setValue(value); 7 } 8 }
至此,簡單的應用程序基本完成,下面可以通過預留的接口來開發具備各種運算功能的插件了。
插件開發
在開發插件之前,我們需要對Qt Creator自動生成的插件項目做一些修改,因為其是基於高階應用生成的。
首先,在EchoPluginLib.pro 文件中的DESTDIR改為
DESTDIR =../plugins
其次,在EchoPluginLib.h 文件中將類EchoPlugin 從繼承自public QGenericPlugin改為繼承自public QObject, public EchoInterface,更改宏Q_PLUGIN_METADATA中IID參數為”EchoPluginTest.EchoPluginLib.EchoInterface”,並添加宏Q_INTERFACES,和重寫接口類的虛函數。
1 class EchoPlugin : public QObject, public EchoInterface 2 { 3 Q_OBJECT 4 #if QT_VERSION >= 0x050000 5 Q_PLUGIN_METADATA(IID "EchoPluginTest.EchoPluginLib.EchoInterface" FILE "EchoPluginLib.json") 6 #endif // QT_VERSION >= 0x050000 7 8 Q_INTERFACES(EchoInterface) 9 10 public: 11 EchoPlugin(QObject *parent = 0); 12 virtual QStringList CalculateType() const override; 13 virtual double Calculate(QString &type,double xvar, double yvar)override; 14 };
宏Q_INTERFACES是必須的,用於告訴qt的meta-object compiler,插件接口的基礎類,如果沒有這個宏,那么在應用程序中,我們無法使用qobject_cast()去檢測接口類。
宏Q_PLUGIN_METADATA用於導出該插件,必須包含插件的IID參數,和可選的參數(用於指向一個包含插件MetaData的Json文件)。
插件中重寫函數的實現
1 QStringList EchoPlugin::CalculateType() const 2 { 3 return QStringList()<< tr("Add")<<tr("Sub"); 4 } 5 double EchoPlugin::Calculate(QString &type, double xvar, double yvar) 6 { 7 if(type == tr("Add")) 8 return xvar + yvar; 9 else if(type == tr("Sub")) 10 return xvar - yvar; 11 else return 0.0; 12 }
至此,一個簡單的插件就開發完成了。
編譯運行
為了項目EchoPluginTest項目編譯子項目的順序(先EchoPluginLib后EchoPluginApp),我們打開EchoPluginTest.pro文件,將其中的SUBDIRS設置如下:
1 SUBDIRS += \ 2 EchoPluginLib \ 3 EchoPluginApp
編譯並運行EchoPluginTest,應用程序EchoPluginApp中可使用插件EchoPluginLib中定義的加法、減法運算。
插件的靜態調用
如需靜態調用插件,以上項目需做如下更改:
1. 在子項目EchoPluginLib中,打開EchoPluginLib.pro文件,在CONFIG后添加static:
CONFIG += plugin static
2.在子項目EchoPluginApp中,打開main.cpp文件,添加宏Q_IMPORT_PLUGIN,如
1 Q_IMPORT_PLUGIN(EchoPlugin) 2 3 int main(int argc, char *argv[]) 4 { 5 QApplication a(argc, argv); 6 MainWindow w; 7 w.show(); 8 9 return a.exec(); 10 }
3.在子項目EchopluginApp中,打開EchoPluginApp.pro文件中添加插件庫。可在EchopluginApp通過右鍵,選擇“添加庫”–>“外部庫”來自動添加。例如
1 win32: LIBS += -L$$PWD/../../***/plugins/ -lEchoPluginLib 2 3 INCLUDEPATH += $$PWD/../../***/plugins 4 DEPENDPATH += $$PWD/../../***/plugins
4.在子項目EchopluginApp項目中的loadplugins函數中使用QPluginLoader::staticInstances()函數來加載插件。
更多的資料可參考官網相關例子:Plug & Paint Example, Plug & Paint Basic Tools Example, Plug & Paint Extra Filters Example, Echo Plugin Example。
如果沒有積分,可從這里下載:https://files-cdn.cnblogs.com/files/warmlight/EchoPluginTest.rar