Qt中為我們提供了兩種開發插件的方式。一種是使用High-Level API接口,一種是使用Low-Level API接口。所謂High-Level API 是指通過繼承Qt為我們提供的特定的插件基類,然后實現一些虛函數、添加需要的宏即可。該種插件開發方式主要是用來擴展Qt庫本身的功能,比如自定義數據庫驅動、圖片格式、文本編碼、自定義樣式等。而我們為自己的應用程序編寫插件來擴展其功能時主要使用第二種方式,即Low-Level API 的方式,該方式不僅能擴展我們自己的應用程序,同樣也能像High-Level API 那樣用來擴展Qt本身的功能。使用這種方式,我們可以將我們需要擴展的功能寫成一個 接口,然后讓一個插件類去實現這個接口的功能,再使用Qt提供的用於插件開發的宏,按Qt要求的格式對插件進行聲明,之后我們就可以在應用程序中使用QPluginLoader 來動態的加載該插件,從而完成應用程序功能的擴展。由於我們平時主要使用插件來擴展我們自己開發的程序,所以今天主要講解一下使用Low-Level API開發插件的方式。至於High-Level API 方式,有需要的同學可以自行研讀Qt的幫助文檔和相關Demo。
想要讓Qt編寫的應用程序支持插件擴展,需要進行一下步驟:
1.定義一系列的接口,應用程序就是使用這些接口與插件進行功能交互的。(標准c++中沒有接口的概念,所以此處的接口指只有純虛函數的類)。
2.使用 Q_DECLARE_INTERFACE() 宏將這個接口的有關信息告訴Qt的元對象系統。
3.在應用程序中使用QPluginLoader 加載這個插件。
4.使用qobject_cast() 函數檢測該插件是否實現了特定的接口。
有了應用程序聲明的接口,我們還需要編寫我們的插件來真正的實現接口所聲明的功能,步驟如下:
1.聲明一個插件類,讓該類繼承QObject 和 應用程序所提供的那個接口。
2.使用Q_INTERFACE() 宏告訴Qt元對象系統這個插件實現了哪些接口。
3.使用Q_PLUGIN_METEDATA() 宏導出這個插件。
4.在.pro 文件的進行相關配置,然后編譯該插件。
上面說到使用Low-Level API接口開發插件所用到的幾個宏定義,下面我們再來詳細的看下每個宏的具體含義。
Q_DECLARE_INTERFACE(ClassName, Identifier):這個宏將一個給定的字符串標識符和ClassName所表示的接口相關聯,其中Identifier必須唯一。例如:
#define BrushInterface_iid "org.qt-project.Qt.Examples.PlugAndPaint.BrushInterface"
Q_DECLARE_INTERFACE(BrushInterface, BrushInterface_iid)
這個宏通常直接在接口所在的頭文件中使用。還有,如果你的接口聲明在了一個名稱空間中,那么你要確保這個宏的使用位於名稱空間外面。例如:
namespace Foo
{
struct MyInterface { ... };
}
Q_DECLARE_INTERFACE(Foo::MyInterface, "org.examples.MyInterface")
Q_IMPORT_PLUGIN(PluginName): 這個宏向應用程序中導入名字為PluginName的插件,這個名字對應於Q_PLUGIN_METADATA() 所在類的類名。這個宏主要用來導入靜態插件。
Q_PLUGIN_METADATA(IID ... FILE ...) :這個宏用來聲明插件的元數據信息。需要傳入被實現接口的IID,和一個保護該插件元數據信息的json文件。注意,這個宏所在的class必須是可默認構造的;另外,FILE是可選的,若傳入了一個json文件,則要確保編譯系統能找到這個的文件,不然,moc(meta-object compiler) 會因為找不到該文件而失敗退出。
剛才講 Q_IMPORT_PLUGIN 時,提到了靜態插件,相對於的也就有動態插件,並且我們使用最多的就是動態插件。下面分別通過一個例子來學習。
動態插件 本質上仍然是一個dll,只不過我們在編寫時根據Qt的要求將其配置成了插件,這樣我們在使用時就可以通過QPluginLoader 來直接加載該dll,並調用其中的函數;並且,在定義插件時不需要寫一堆的函數導出聲明。下面,為了便於測試,我們在QtCreator 中新建一個子目錄項目(用於包含其他項目的項目,類似於vs的解決方案)並且添加兩個項目,一個是dll項目,一個是測試項目。步驟如下:
啟動QtCreator,點擊文件->新建文件或項目,選擇其他項目->子目錄項目
輸入工程名即可,建立好后,如下:
此時項目為空,因為沒有添加子項目。
在工程上 右鍵->新的子項目,先添加一個測試插件的項目test,如下
選擇 QWidget 作為我們窗口的基類,如下:
同理,在DynamicPlugin上點右鍵->新的子項目,在此我選擇一個空的qmake項目作為我們的插件項目,如下:
最終的項目結構如下:
然后,在test工程上右鍵->添加新文件,添加一個c++頭文件interface.h,即我們的接口文件,一會就讓我們的插件類實現這個接口。
該文件的內容如下:
#ifndef INTERFACE_H
#define INTERFACE_H
#include <QWidget>
class PluginInterface
{
public:
virtual ~PluginInterface() {}
virtual void SayHello(QWidget *parent) = 0;
};
#define pluginInterface_iid "io.qt.dynamicplugin"
Q_DECLARE_INTERFACE(PluginInterface, pluginInterface_iid)
#endif // INTERFACE_H
在此,為簡單起見,我們只定義了一個SayHello() 純虛函數,並使用Q_DECLARE_INTERFACE宏向Qt元對象系統聲明了這個接口。
然后,在plugin工程上點右鍵->添加新文件->c++類,新建一個plugin類,讓其繼承QObject和我們自定義的接口,並實現SayHello() 純虛函數。plugin.h內容如下:
#ifndef PLUGIN_H
#define PLUGIN_H
#include <QObject>
#include "../test/interface.h"
class plugin : public QObject, PluginInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID pluginInterface_iid FILE "plugin.json")
Q_INTERFACES(PluginInterface)
public:
void SayHello(QWidget *parent) Q_DECL_OVERRIDE;
};
#endif // PLUGIN_H
在此,我們同時使用相關宏向Qt元對象系統聲明了該插件的相關信息。當然我們還要新建一個json文件,目前我們只想在plugin.json中寫一個表示json格式的{} 即可。其實現文件如下:
void Plugin::SayHello(QWidget *parent)
{
QMessageBox::information(parent, "Plugin", "Hello, I'm dynamically loaded.");
}
為簡單起見,我在此只彈出一個消息框。
最后也是最重要的一步,就是通過.pro文件,將該項目配置成動態插件,如下:
QT += widgets
TEMPLATE = lib
CONFIG += plugin
HEADERS += \
plugin.h
SOURCES += \
plugin.cpp
DISTFILES += \
plugin.json
其中,TEMPLATE指明這是一個dll工程,不是一個exe工程;config就是用類配置該工程為插件的。
構建該工程,即可在磁盤上生成該插件對應的dll。
接下來,我們在test工程中測試該插件。首先,在test工程的窗口上放一個按鈕,並為該按鈕關聯一個槽函數。所實現的功能就是當點擊按鈕時,加載插件並調用SayHello() 彈出一個對話框。槽函數內容如下:
void Widget::OnClick()
{
PluginInterface *interface = nullptr;
QPluginLoader pluginLoader("plugin.dll");
QObject *plugin = pluginLoader.instance();
if(plugin)
{
interface = qobject_cast<PluginInterface*>(plugin);
if(interface)
{
interface->SayHello(this);
}
}
}
其中我們先定義了一個插件接口的指針,然后使用QPluginLoader 動態加載我們剛才生成的插件(若不在當前文件夾 下,需指明具體路徑),在通過instance() 函數生成一個插件指針,若生成成功,在嘗試將該指針轉成我們實際需要的插件類型,然后調用插件的SayHello() 函數,彈出對話框。運行如下:
至此,動態插件的開發實例就完成了。
靜態插件
上面我們開發動態插件時說過,動態插件其實也是一個dll文件,同理,靜態插件其實也就是一個lib文件。所以,我們還以上面的例子來說明。仿照上面的過程,新建一個StaticPlugin的子目錄工程,並新建好相關文件。然后,只需要修改三個地方即可實現靜態插件的開發。
1.修改plugin工程的pro文件,在config后面添加static配置,即:CONFIG += plugin static
2.修改test工程的pro文件,添加 LIBS += ./libplugin.a,即為test工程引入靜態插件所對應的.a文件(gcc)或.lib文件(vs)。若文件不在當前目錄下,則需指定具體路徑。
3.在main() 函數前添加 Q_IMPORT_PLUGIN(Plugin),即導入靜態插件。
其使用方式如下:
void Widget::OnClick()
{
PluginInterface *interface = nullptr;
foreach (QObject *plugin, QPluginLoader::staticInstances())
{
interface = qobject_cast<PluginInterface*>(plugin);
if(interface)
{
interface->SayHello(this);
}
}
}
通過QPluginLoader的靜態方法staticInstances()使用加載到當前工程的所有靜態插件。我們只需通過遍歷,找到我們所需要的特定類型的插件即可。測試結果如下:
---------------------
作者:求道玉
來源:CSDN
原文:https://blog.csdn.net/Amnes1a/article/details/62223210
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!