1. 程式人生 > >Qt外掛開發入門

Qt外掛開發入門

       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()使用載入到當前工程的所有靜態外掛。我們只需通過遍歷,找到我們所需要的特定型別的外掛即可。測試結果如下: