Qt 5 在外掛中如何使用訊號槽機制
對於一個大型系統,如何保證可擴充套件性和可維護性是十分重要的。Qt為我們提供了一套外掛系統,能夠較好的解決擴充套件性的問題。但是在將外掛系統與訊號槽機制相結合的過程中,也遇到了一些問題。經過一番探索之後總算成功了,這裡寫一個小小的教程,供有需要的同學查閱。
一、概述
Qt的外掛系統分為High-Level API介面和Low-Level API介面。
所謂High-Level API 是指通過繼承Qt為我們提供的特定的外掛基類,然後實現一些虛擬函式、新增需要的巨集即可。該種外掛開發方式主要是用來擴充套件Qt庫本身的功能,比如自定義資料庫驅動、圖片格式、文字編碼、自定義樣式等。而我們為自己的應用程式編寫外掛來擴充套件其功能時主要使用第二種方式
在本文中,我們使用Low-Level API介面進行外掛的編寫。有關High-Level相關內容,參閱Qt官方說明文件。
除此之外,外掛還有靜態和動態之分。靜態外掛顧名思義,就是編譯出一個lib作為外掛,在程式中靜態編譯。動態外掛則是一個dll(windows下),方便擴充套件和維護。因此我們使用動態外掛作為說明。
總的來說,外掛擴充套件並使用訊號槽機制需要以下步驟:
- 定義一個外掛介面類,作為主程式與外掛之間通訊的橋樑,定義普通成員函式(純虛擬函式)、槽函式(純虛擬函式)、訊號(
- 繼承介面類,實現其中的虛擬函式。並:a) 使用 Q_PLUGIN_METADATA()巨集匯出外掛;b) 使用Q_INTERFACES()通知元物件系統此外掛使用了哪些介面類;
- 在主程式中利用QPluginLoader載入外掛,使用QPluginLoader::instance()方法例項化外掛,使用qobject_cast強制轉換為介面類指標,connect有關訊號和槽;
- 開始愉快的使用;
二、實現
2.1 工程(Qt creator)
2.1.1 新建專案
新建一個子專案目錄,這個專案將包含主工程、介面和外掛三個子工程。這裡我們起名為PluginTest
2.1.2 新建主程式工程
主程式。起名叫App。右鍵PluginTest,選新建子專案,選Qt Widgets Application
2.1.3 新建介面工程
起名叫PluginInterface。右鍵PluginTest,選新建子專案, 在“專案”框中選library,右邊只有一個專案可選
選共享庫。
一路下一步,自動生成四個檔案。
2.1.4 新建外掛工程
起名叫myPlugin。右鍵PluginTest,選新建子專案,選Empty qmake Project
會提示個這,不用管它,因為這個工程裡沒有任何檔案。
到此為止,外掛的框架就搭好了。總結一下,我們用了三個子工程:
- 主程式:Qt Widgets Application,用來生成一個exe呼叫外掛
- 介面:c++共享庫,用來生成一個lib向編譯器提供介面資訊
- 外掛:空qmake工程,後面手動對其進行填寫,生成dll作為主程式的外掛
另外,c++庫中的qt plugin選項是生成High-Level API的,不用在這裡。
2.2 介面類
工程是PluginInterface。plugininterface_global.h是自動生成的,這裡我們無需修改它。
plugininterface.h:
//plugininterface.h
#ifndef PLUGININTERFACE_H
#define PLUGININTERFACE_H
#include "plugininterface_global.h"
#include <QObject>
//介面類
class PLUGININTERFACESHARED_EXPORT PluginInterface : public QObject
{
Q_OBJECT
public:
virtual ~PluginInterface() {} //虛解構函式,c++多型
public slots:
virtual void SayHello(QWidget *parent) = 0;//虛槽函式,而且是純的
signals:
void doSomething();//訊號不能為虛
};
#define InterfaceIID "interface"
Q_DECLARE_INTERFACE(PluginInterface, InterfaceIID)
#endif // PLUGININTERFACE_H
因為不需要對其進行實現,所以plugininterface.cpp就可以刪掉了
注意到我們將槽函式宣告成為一個純虛擬函式,使得介面類成為了一個抽象類。增強了程式碼的健壯性。
又注意到訊號是一個普通成員函式的宣告,訊號不能是虛的,否則連線不過。(moc相關,就不展開講了)
我們在pro檔案中指定生成的目錄,方便其他工程對其進行引用:PluginInterface.pro:
#PluginInterface.pro
QT -= gui
TARGET = PluginInterface
TEMPLATE = lib
DEFINES += PLUGININTERFACE_LIBRARY
# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES +=
HEADERS += \
plugininterface.h \
plugininterface_global.h
unix {
target.path = /usr/lib
INSTALLS += target
}
###修改生成目錄,到pro檔案目錄/lib中
DESTDIR = $$PWD/lib
2.3 外掛類
首先向其新增一個類:右鍵myPlugin,新增新檔案,c++class,起名為Plugin,繼承PluginInterface
然後修改pro檔案:
#myPlugin.pro
HEADERS += \
plugin.h
SOURCES += \
plugin.cpp
QT += widgets
TARGET = Plugin#型別是plugin
TEMPLATE = lib#模板是lib
INCLUDEPATH += $$PWD/../PluginInterface#指定了包含目錄
DEPENDPATH += $$PWD/../PluginInterface#指定了附加依賴項目錄
LIBS += -L$$PWD/../PluginInterface/lib/ -lPluginInterface#要新增介面生成的庫給編譯器
DESTDIR = ../app/debug#生成的dll直接扔到app的目錄下
plugin.h:
//plugin.h
#ifndef PLUGIN_H
#define PLUGIN_H
#include "plugininterface.h"
#include <QWidget>
class Plugin : public PluginInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "my.test.plugin.interface")//匯出plugin
Q_INTERFACES(PluginInterface)
public:
void SayHello(QWidget *parent) Q_DECL_OVERRIDE;//宣告是重寫虛擬函式
};
#endif // PLUGIN_H
注意到需要一個Q_OBJECT巨集。
又注意到Q_PLUGIN_METADATA用來匯出外掛,Q_INTERFACES宣告使用的介面。
還注意到虛擬函式聲明後邊加了一個Q_DECL_OVERRIDE,用來向編譯器說明這是個重寫的虛擬函式。
plugin.cpp:
//plugin.cpp
#include "plugin.h"
#include "QMessageBox"
void Plugin::SayHello(QWidget *parent)
{
emit doSomething();
QMessageBox::information(parent, "123", "12345");
}
注意到,在plugin中直接emit了doSomething訊號。
2.4 主程式
修改pro 檔案:
#App.pro
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = App
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
widget.cpp
HEADERS += \
widget.h
FORMS += \
widget.ui
LIBS += -L$$PWD/../PluginInterface/lib/ -lPluginInterface
INCLUDEPATH += $$PWD/../PluginInterface
DEPENDPATH += $$PWD/../PluginInterface
跟外掛類類似,添加了介面類的lib、包含目錄和附加依賴項。
在ui中新增兩個按鈕:
然後是widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QPluginLoader>
#include "plugininterface.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();
void onDoSomething();
signals:
void saySomething(QWidget *);
private:
Ui::Widget *ui;
PluginInterface *interface = nullptr;
QPluginLoader pluginLoader;
};
#endif // WIDGET_H
widget.cpp:
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
if(!interface)
{
pluginLoader.setFileName("Plugin.dll");
QObject *plugin = pluginLoader.instance();
if(plugin)
{
interface = qobject_cast<PluginInterface*>(plugin);//使用多型,將基類指標強制轉換成派生類指標,檢查能否轉換
if(interface)
{
connect(this, &Widget::saySomething, interface, &PluginInterface::SayHello);
connect(interface, &PluginInterface::doSomething, this, &Widget::onDoSomething);
}
else
{
QMessageBox::critical(this, "err", "this is not a proper plugin");
return;
}
}
else
{
QMessageBox::critical(this, "err", "could not find any plugin");
return;
}
}
emit saySomething(this);
}
void Widget::on_pushButton_2_clicked()
{
if(interface)
{
if (pluginLoader.unload())
{
interface = nullptr;
QMessageBox::information(this,"info","unloaded");
}
else
QMessageBox::critical(this,"err", "unload failed");
}
}
void Widget::onDoSomething()
{
QMessageBox::information(this,"info","you want to do something");
}