Qt 编写插件plugin,支持接口定义信号
https://blog.csdn.net/u014213012/article/details/122434193?spm=1001.2014.3001.5506
本教程基于该链接的内容进行升级,在编写插件的基础上,支持接口类定义信号。
环境:Qt5.12.12 + MSVC2017
一、创建项目
- 新建一个子项目便于程序管理【文件->新建文件或项目->其它项目->子目录项目】
2.本次演示项目命名为【PluginProject】,然后指定目录
3.指定构建套件
4.完成并添加应用程序项目
二、创建调试项目
1.做好上一个步骤后,会自动弹窗新建子项目【Application->Qt Widgets Application】
2.本次演示应用程序命名为【MainApp】默认mainwindow窗口类
3.创建完成的项目结构如下
4.【项目->关掉shadow build】免得后面找不到插件路径
三、创建插件项目
1.【PluginProject->右键->新子项目->Application->Qt Widgets Application】
- 项目名为【PluginApp】
3.基类选择QWidget,基类改名为【PluginWidget】
4.完成后的项目结构
5.在PluginApp.pro中添加以下配置:
TARGET = PluginApp
TEMPLATE =lib #template改app为lib
CONFIG += plugin #增加plugin的配置
DESTDIR = ../MainApp/debug/plugin #目标直接生成到MainApp的debug/plugin
6.【PluginApp->右键->新增C++ Header File】命名为abstractinterface
7.abstractinterface是对外暴漏的接口类
类里的函数必须是纯虚的,使用Q_DECLARE_INTERFACE()宏向Qt的元对象系统声明该接口
为了能在接口暴漏信号,必须使用Q_OBJECT宏,因此该类需要继承QObject
#ifndef ABSTRACTINTERFACE_H
#define ABSTRACTINTERFACE_H
#include <QtPlugin>
class QWidget;
class AbstractInterface:public QObject{
Q_OBJECT
public:
virtual ~AbstractInterface(){} //必须定义虚析构函数
virtual QWidget* createWidgetPlugin(QWidget *parent) = 0;
signals:
virtual void printMessage(QString text) = 0;
};
#define AbstarctInterface_IID "qt.org.com.abstactinterface/1.0" //iid随便命名当前项目独一无二即可
Q_DECLARE_INTERFACE(AbstractInterface,AbstarctInterface_IID) //声明接口
#endif // ABSTRACTINTERFACE_H
8.【PluginApp->右键->新增C++ Class】命名为InterfaceImplement
9.InterfaceImplement是接口实现类,继承AbstractInterface。
使用Q_INTERFACES()宏告诉Qt的元对象系统有关接口的信息
使用Q_PLUGIN_METADATA ()宏导出插件。
#ifndef INTERFACEIMPLEMENT_H
#define INTERFACEIMPLEMENT_H
#include"abstractinterface.h"
class InterfaceImplement:public AbstractInterface
{
public:
Q_OBJECT
Q_INTERFACES(AbstractInterface) //实现的接口
Q_PLUGIN_METADATA(IID AbstarctInterface_IID) //导出接口后面的 FILE “jsonfile”可省略
public:
explicit InterfaceImplement();
~InterfaceImplement() override;
QWidget *createWidgetPlugin(QWidget *parent) override;
signals:
void printMessage(QString text) override;
};
#endif // INTERFACEIMPLEMENT_H
10.双击PluginWidget.ui,拖个按钮
14.右键PushButton->转到槽->选择clicked()->OK
15. PluginWidget.h添加:
signals:
void sendMessage(QString text);
- PluginWidget.cpp的on_pushButton_clicked槽函数内,发射”hello world字符:
emit sendMessage("hello world");
17.在InterfaceImplement.cpp实现createWidgetPlugin,返回PluginWidget的实例,转发信号
#include "interfaceimplement.h"
#include "pluginwidget.h"
InterfaceImplement::InterfaceImplement()
{
}
InterfaceImplement::~InterfaceImplement()
{
}
QWidget *InterfaceImplement::createWidgetPlugin(QWidget *parent)
{
PluginWidget * w = new PluginWidget(parent); //返回PluginApp实现的界面类
connect(w, &PluginWidget::sendMessage, this, [this](const QString &text) {
emit printMessage(text);
});
return w;
}
四.调试插件
1.MainApp.pro添加抽象类所在的目录:
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
INCLUDEPATH += ../PluginApp #包含PluginApp创建的HandEyePositionInterface.h
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h
FORMS += \
mainwindow.ui
2.右键MainApp->执行qmake
3.点开MainApp下的mainwindow.h,使用QPluginLoader加载插件.
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class AbstractInterface;
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void print(QString text);
private:
Ui::MainWindow *ui;
private:
void loadPlugin(); //应用程序装载插件
private:
AbstractInterface *mainInterface; //插件类
};
#endif // MAINWINDOW_H
4.在loadPlugin()中加载插件。注意连接插件信号的connect要用SIGNAL() SLOT()的方式连
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "abstractinterface.h"
#include <QDir>
#include <QPluginLoader> //包含插件装载头文件
#include <QVBoxLayout>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
loadPlugin();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::print(QString text)
{
qDebug() << "Received message from plugin:" << text;
}
void MainWindow::loadPlugin()
{
//插件存放在应用程序目录下的plugin目录下,便于管理
QDir pluginDir(qApp->applicationDirPath()); //定位应用程序目录
if( pluginDir.cd("plugin") ) //进入plugin,前提先创建plugin目录
{
for(auto fileName:pluginDir.entryList(QDir::Files)) //遍历目录下的文件
{
QFileInfo info(fileName);
if(info.completeSuffix() == "dll" || info.completeSuffix() == "so") //过滤后缀为dll或so的动态库文件
{
QPluginLoader load(pluginDir.absoluteFilePath(fileName)); //装载插件
QObject *pluginObj = load.instance(); //获取插件根对象
if(pluginObj)
{
mainInterface = qobject_cast<AbstractInterface *>(pluginObj); //转换
QWidget*w =mainInterface->createWidgetPlugin(this);
connect(mainInterface, SIGNAL(printMessage(QString)), this, SLOT(print(QString)));//注意,使用字符串连接的方式,避免编译阶段进行类型检查
if (!ui->centralwidget->layout())
{
ui->centralwidget->setLayout(new QVBoxLayout());
}
ui->centralwidget->layout()->addWidget(w); // 将插件小部件添加到 centralwidget 的布局中
}
}
}
}
}
5.运行后点按钮,看输出效果
五.其他说明
如果插件类和实现类用到了三方库,以本项目为例,PluginApp.pro添加了三方库3rdParty.pri。那么在调试类,也需要包含同样的三方库,也就是本项目的MainApp.pro也需要包含这个3rdParty.pri,才能正常使用。