对于一个大型系统,如何保证可扩展性和可维护性是十分重要的。Qt为我们提供了一套插件系统,能够较好的解决扩展性的问题。但是在将插件系统与信号槽机制相结合的过程中,也遇到了一些问题。经过一番探索之后总算成功了,这里写一个小小的教程,供有需要的同学查阅。
更新:源代码已上传到 github csdn
Qt的插件系统分为High-Level API接口和Low-Level API接口。
所谓High-Level API 是指通过继承Qt为我们提供的特定的插件基类,然后实现一些虚函数、添加需要的宏即可。该种插件开发方式主要是用来扩展Qt库本身的功能,比如自定义数据库驱动、图片格式、文本编码、自定义样式等。而我们为自己的应用程序编写插件来扩展其功能时主要使用第二种方式,即Low-Level API 的方式,该方式不仅能扩展我们自己的应用程序,同样也能像High-Level API 那样用来扩展Qt本身的功能。
在本文中,我们使用Low-Level API接口进行插件的编写。有关High-Level相关内容,参阅Qt官方说明文档。
除此之外,插件还有静态和动态之分。静态插件顾名思义,就是编译出一个lib作为插件,在程序中静态编译。动态插件则是一个dll(windows下),方便扩展和维护。因此我们使用动态插件作为说明。
总的来说,插件扩展并使用信号槽机制需要以下步骤:
注意:主程序、接口、插件应分别作为3个子工程。主程序和插件需要调用接口生成的lib文件,否则会出现未定义的外部符号错误。https://stackoverflow.com/questions/50516359/declaring-signals-in-interface-class-using-qt-plugin-system-with-new-signal-slot
新建一个子项目目录,这个项目将包含主工程、接口和插件三个子工程。这里我们起名为PluginTest
主程序。起名叫App。右键PluginTest,选新建子项目,选Qt Widgets Application
起名叫PluginInterface。右键PluginTest,选新建子项目, 在“项目”框中选library,右边只有一个项目可选
选c++库选共享库。
一路下一步,自动生成四个文件。
起名叫myPlugin。右键PluginTest,选新建子项目,选Empty qmake Project
会提示个这,不用管它,因为这个工程里没有任何文件。
到此为止,插件的框架就搭好了。总结一下,我们用了三个子工程:
另外,c++库中的qt plugin选项是生成High-Level API的,不用在这里。
工程是PluginInterface。plugininterface_global.h是自动生成的,这里我们无需修改它。
plugininterface.h:
//plugininterface.h
#ifndef PLUGININTERFACE_H
#define PLUGININTERFACE_H
#include "plugininterface_global.h"
#include
//接口类
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
首先向其添加一个类:右键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
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信号。
修改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
#include
#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
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(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");
}