說明
近期入職新公司,新公司的項目用到了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里面使用宏定義替代。
步驟:
- 定義抽象類,並聲明需要用到的接口;
- 使用 Q_DECLARE_INTERFACE() 宏定義將這個抽象類的信息告訴Qt的元對象系統;
- 建立dll項目,繼承自 QObject 和這個抽象類,並實現其接口,我們稱這個項目為插件;
- 在插件的頭文件中使用 Q_PLUGIN_METADATA() 宏定義導出這個插件;
- 在插件項目的.pro文件中配置導出設置,編譯生成dll;
- 在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() 方法釋放。