Qt插件系統


說明

近期入職新公司,新公司的項目用到了Qt的插件系統,花時間了解了一下,還以為Qt的插件系統有多么高級呢,原來歸根到底還是 dll 的動態調用時獲取其中的類那一招啊,原理和之前的文章《DLL的動態加載》 的里使用 使用dll中的類 描述的方法如出一轍,只是Qt利用了其庫的優勢。

動態加載dll獲取類

《DLL的動態加載》 文章說已經說明,dll只可以導出函數,不可以導出指針,但是為了能得到dll中的類,可以導出一個接口,使用接口獲取對象指針。但是在dll的調用一方,卻無法識別獲取到的類指針,所以利用C++的多態和繼承的特性來完成。使用抽象類,聲明好抽象接口,將需要導出的類繼承自該抽象類,並實現其聲明的接口。dll的導出方和調用方公用一個抽象類聲明文件,在dll導出函數內部,利用多態,將抽象的類指針new成子類對象,並返回出來,則調用方就可以識別得到的類指針了。具體方法說明請參考文章《DLL的動態加載》

Qt的插件系統

Qt的插件系統原理(這里只討論High-Level API)與此相同,只是Qt利用了其內部非特性,使用QPluginLoader替代了導出類指針的接口,並使用宏定義進行導入和導出,避免了在導出導出上因為為了兼容C語言而導致的一些問題,並簡化了一些操作。

使用方法

既然原理相同,則需要的文件也相同,需要導出的dll文件和抽象類的聲明文件。不同點是,不需要在使用C風格的函數導出類指針了,Qt里面使用宏定義替代。

步驟:

  1. 定義抽象類,並聲明需要用到的接口;
  2. 使用 Q_DECLARE_INTERFACE() 宏定義將這個抽象類的信息告訴Qt的元對象系統;
  3. 建立dll項目,繼承自 QObject 和這個抽象類,並實現其接口,我們稱這個項目為插件;
  4. 在插件的頭文件中使用 Q_PLUGIN_METADATA() 宏定義導出這個插件;
  5. 在插件項目的.pro文件中配置導出設置,編譯生成dll;
  6. 在dll調用程序中加載dll;

示例代碼:

插件項目plugin:
HEADERS += \
    plugin.h

SOURCES += \
    plugin.cpp

QT += widgets

DISTFILES += \
    plugin.json

CONFIG += plugin    #表示用於lib模板:庫是一個插件
TEMPLATE = lib      #表示生成庫文件
//plugin.h
class Plugin : public QObject, public PluginInterface
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID pluginInterface_iid FILE "plugin.json")
    Q_INTERFACES(PluginInterface)
public:
    explicit Plugin(QObject *parent = 0);
    ~Plugin();
    void SayHello(QWidget* parent) Q_DECL_OVERRIDE;
};

說明:

  • 必須多繼承自 QObject 和抽象類;
  • Q_PLUGIN_METADATA 用於聲明元數據,IID 是必須且唯一的,FILE 是可選的,后面跟着一個json文件,用於描述插件的相關數據信息;
  • Q_INTERFACES 的作用是將所實現的插件接口通知給元類型系統,參數是抽象類類名;
  • Q_DECL_OVERRIDE 用於表示這是一個虛函數,編譯器會檢查這個方法是不是父類中所有的,若沒有則報錯(類似java中的@Override注解);
//plugin.cpp
Plugin::Plugin(QObject *parent) : QObject(parent)
{
    qDebug()<<"constructor";
}

Plugin::~Plugin()
{
    qDebug()<<"destructor";
}

void Plugin::SayHello(QWidget *parent)
{
    QMessageBox::information(parent, "plugin", "hello, this is dynamically loaded.");
}
{
    "Keys" : [ "plugin" ]
}
調用項目test:

插件生成的dll文件copy到執行目錄下,共調用方加載

//interface.h
class PluginInterface
{
public:
    virtual ~PluginInterface(){}
    virtual void SayHello(QWidget* parent) = 0;
};

#define pluginInterface_iid "io.qt.dynamicplugin"
Q_DECLARE_INTERFACE(PluginInterface, pluginInterface_iid)

說明:

  • Q_DECLARE_INTERFACE 宏告訴Qt,這是一個插件接口,第一個參數是接口類名,第二個參數是插件標識符,大小寫敏感,且唯一;
//調用部分
PluginInterface* interface = NULL;
QPluginLoader plugin_loader("plugin.dll");
QObject* plugin = plugin_loader.instance();
if(plugin)
{
	interface = qobject_cast<PluginInterface*>(plugin);
	if(interface)
    {
		interface->SayHello(this);
	}
	//delete plugin;
	plugin_loader.unload();
}

注:Qt文檔中說明,QPluginLoader 釋放之后,其獲取到的插件不會釋放,所以,需要手動 delete 插件,或者使用 unload() 方法釋放。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM