Qt Plugin插件开发指南(1)- 一般开发流程

Date Author Version Note
2020.02.17 Dog Tao V1.0 整理后发表。
2020.12.10 Dog Tao V1.1 1. 增加了插件中信号的实现。 2. 修改了部分源码。
2021.04.06 Dog Tao V1.2 1. 增加了插件绑定信号与槽的说明。
2022.04.08 Dog Tao V1.3 1. 增加了插件信号发射自定类型变量的说明。

文章目录

  • Qt Plugin插件开发指南(1)- 一般开发流程
    • 概述
      • Qt插件简介
      • Qt插件开发的流程
      • Qt插件调用的流程
    • 插件的开发
      • 设计接口文件
      • 配置工程文件
      • 实现接口的类
      • 插件的调用
    • 特别说明
      • 绑定信号与槽
      • 发射自定类型变量
      • 注册回调函数

概述

Qt插件简介

插件是一种遵循一定规范的应用程序接口编写出来的程序,定位于开发实现应用软件平台不具备的功能的程序。插件与宿主程序之间通过接口联系,就像硬件插卡一样,可以被随时删除,插入和修改,所以结构很灵活,容易修改,方便软件的升级和维护。Qt提供了两种API用于创建插件:一种是高阶API,用于扩展Qt本身的功能,如自定义数据库驱动,图像格式,文本编码,自定义样式等;一种是低阶API,用于扩展Qt应用程序。本文主要是通过低阶API来创建Qt插件,并通过静态、动态两种方式来调用插件。

Qt插件开发的流程

  1. 定义一个接口集(只有纯虚函数的类)。
  2. 用宏Q_DECLARE_INTERFACE()将该接口告诉Qt元对象系统
  3. 声明插件类,插件类继承自QObject和插件实现的接口。
  4. 用宏Q_INTERFACES()将插件接口告诉Qt元对象系统(在头文件中)。
  5. 用宏Q_EXPORT_PLUGIN2()导出插件类。
  6. 用适当的.pro文件构建插件。

Qt插件调用的流程

  1. 包含接口头文件(只有纯虚函数的类)。
  2. 应用程序中用QPluginLoader来加载插件。
  3. 用宏qobject_cast()来判断一个插件是否实现了接口。

参考文章1 参考文章2

插件的开发

Qt插件开发主要包括 声明接口文件、建立工程文件、声明和定义实现接口的类等步骤。

设计接口文件

接口文件在插件开发、插件调用中都需要引用。接口的方法需要定义成纯虚函数。值得注意的是,Qt插件也是支持信号与槽的机制,在接口文件中信号也同样被声明为纯虚函数。注意源码中使用了Q_DECL_OVERRIDE宏确保重写了虚函数。

#ifndef ISERIALPORT_H
#define ISERIALPORT_H
#include <QString>
#include <QtPlugin>
#include <QObject>
#include <QList>
#include <functional>
/*
宏定义 接口IID,用来唯一标记该接口类。实际开发中,IID的名称为了避免重复,推荐采用本例所示的方式命名
*/
#define QTPLUGIN_ISERIALPORT_IID "ewhales.plugin.interface.serialport"using namespace std;
using namespace placeholders;/*
该处省略与插件无关的业务代码
*//*
std::function对象用于实现函数回调,下面会详细说明。
*/
typedef std::function<void(const unsigned char *,int count)>FUNdataReceive;
typedef std::function<void(const QList<QString> &)>FUNportChange;/*
接口需要定义成纯虚函数
*/
class ISerialPort
{public:virtual void GetPortList(QStringList &portList)=0;virtual void GetSerialPortConfig(SerialPort_Typedef &serialPort_Typedef)=0;virtual void SetSerialPort(SerialPort_Typedef *serialPort_Typedef)=0;virtual int OpenSerialPort()=0;virtual int CloseSerialPort()=0;virtual void StartListening()=0;virtual void StopListening()=0;virtual int SendData(QByteArray data)=0;virtual int SendData(QString string)=0;virtual void SetPortChangedHandler(FUNportChange fPortChange)=0;virtual void SetDataReceivedHandler(FUNdataReceive fDataRecv)=0;virtual QWidget *GetPanel()=0;//Override as signals.virtual void Signal_1(QString data1)=0;virtual void Signal_2(QString data2)=0;
};/*
为了能够在运行时查询插件是否实现给定的接口,我们必须使用宏Q_DECLARE_INTERFACE(),该宏的第一参数为接口类的名称,第二个参数是一个字符串,用于唯一标记该接口类。
*/
Q_DECLARE_INTERFACE (ISerialPort, QTPLUGIN_ISERIALPORT_IID)#endif // EW_SERIALPORT_INTERFACE

配置工程文件

#-------------------------------------------------
#
# Project created by QtCreator 2018-05-31T15:19:56
#TEMPLATE = lib
#-------------------------------------------------QT       += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgetsQMAKE_CXXFLAGS += -std=c++11
TARGET = EWhalesSerialPort# TEMPLATE = lib 生成插件
# TEMPLATE = app 生成应用程序
TEMPLATE = lib
CONFIG  += pluginSOURCES += main.cpp\serialport.core.cpp \serialport.pannel.cpp \serialport.framework.cpp \serialport.thread.cppHEADERS  += \serialport.core.h \serialport.interface.h \serialport.pannel.h \serialport.framework.h \serialport.thread.hFORMS    += widget.uiunix {#target.path += /root/target.path =/usr/libINSTALLS += target
}#target.path += /root/
#INSTALLS += targetRESOURCES += \resource.qrc

实现接口的类

头文件

#ifndef SERIALPORTINTERACTIVE_H
#define SERIALPORTINTERACTIVE_H#include <QObject>
#include <functional>
#include <QFileSystemWatcher>
#include <QMetaEnum>
#include "serialport.pannel.h"
#include "serialport.interface.h"
#include "serialport.core.h"
#include "serialport.thread.h"
#include "serialport.pannel.h"/*
实现插件的类必须继承自插件接口类
*/
class SerialPortInteractive : public QObject, public ISerialPort
{Q_OBJECT/*使用Q_INTERFACES声明:类支持ISerialPort*/Q_INTERFACES(ISerialPort)/*
Qt4与Qt5的插件开发方式略有差异,此处采用条件编译可以实现版本兼容
*/
#if QT_VERSION >= 0x050000Q_PLUGIN_METADATA(IID QTPLUGIN_ISERIALPORT_IID)
#endifpublic:/*此处省略了与插件开发无关的代码*/SerialPortInteractive();~SerialPortInteractive();void GetPortList(QStringList &portList) Q_DECL_OVERRIDE;void GetSerialPortConfig(SerialPort_Typedef &serialPort_Typedef) Q_DECL_OVERRIDE;void SetSerialPort(SerialPort_Typedef *serialPort_Typedef) Q_DECL_OVERRIDE;int OpenSerialPort() Q_DECL_OVERRIDE;int CloseSerialPort() Q_DECL_OVERRIDE;void StartListening() Q_DECL_OVERRIDE;void StopListening() Q_DECL_OVERRIDE;    int SendData(QByteArray data) Q_DECL_OVERRIDE;int SendData(QString string) Q_DECL_OVERRIDE;void SetPortChangedHandler(FUNportChange fPortChange) Q_DECL_OVERRIDE;void SetDataReceivedHandler(FUNdataReceive fDataRecv) Q_DECL_OVERRIDE;QWidget* GetPanel() Q_DECL_OVERRIDE;void ShowPanel(QWidget *parent) Q_DECL_OVERRIDE;
private:QList<QString> spList;QFileSystemWatcher watcher;SerialPort_Typedef *sp_Typedef;serialportCore *port;Pannel *panel;BackgroundThread *thread;FUNportChange _fportchange;FUNdataReceive _fdatarecv;
private slots:void _detectPortChange();void _receiveData(const unsigned char *data, int count);signals:void Signal_1(QString data1) Q_DECL_OVERRIDE;void Signal_2(QString data2) Q_DECL_OVERRIDE;
};#endif // SERIALPORT_FRAMEWORK_H

源文件

#include "serialport.framework.h"
#include <QList>
#include <QDebug>SerialPortInteractive::SerialPortInteractive()
{/*类构造函数此处省略了与插件开发无关的代码*/
}SerialPortInteractive::~SerialPortInteractive()
{/*类析构函数此处省略了与插件开发无关的代码*/
}
void SerialPortInteractive::ShowPanel(QWidget *parent)
{panel->setParent(parent);panel->show();
}QWidget *SerialPortInteractive::GetPanel()
{return panel;
}void SerialPortInteractive::GetPortList(QStringList &portList)
{QString str=port->GetPortList().trimmed();portList=str.split("\n");
}void SerialPortInteractive::GetSerialPortConfig(SerialPort_Typedef &serialPort_Typedef)
{serialPort_Typedef=*sp_Typedef;
}void SerialPortInteractive::SetSerialPort(SerialPort_Typedef *serialPort_Typedef)
{sp_Typedef=serialPort_Typedef;
}int SerialPortInteractive::OpenSerialPort()
{int i =port->OpenPort(sp_Typedef);return i;
}int SerialPortInteractive::CloseSerialPort()
{int i=port->ClosePort();return i;
}void SerialPortInteractive::StartListening()
{thread->start();
}void SerialPortInteractive::StopListening()
{thread->stop();
}int SerialPortInteractive::SendData(QByteArray data)
{int i=port->SendData(data);return i;
}int SerialPortInteractive::SendData(QString string)
{int i=port->SendData(string);return i;
}void SerialPortInteractive::SetPortChangedHandler(FUNportChange fPortChange)
{_fportchange=fPortChange;
}void SerialPortInteractive::SetDataReceivedHandler(FUNdataReceive fDataRecv)
{_fdatarecv=fDataRecv;
}void SerialPortInteractive::_detectPortChange()
{QStringList portList;GetPortList(portList);if(_fportchange!=NULL)_fportchange(portList);emit Signal_1("data1");
}void SerialPortInteractive::_receiveData(const unsigned char *data,int count)
{if(_fdatarecv!=NULL)_fdatarecv(data,count);emit Signal_2("data2");
}/*
Qt4与Qt5的插件开发方式略有差异,此处采用条件编译可以实现版本兼容。
导出Qt插件,第一参数为插件的IID,第二个参数为实现接口的类。
*/
#if QT_VERSION < 0x050000Q_EXPORT_PLUGIN2(QTPLUGIN_ISERIALPORT_IID,SerialPortInteractive)
#endif

插件的调用

示例为动态调用插件的方法。对于静态调用方法不推荐使用。

bool Widget::loadSerialPortPlugin()
{QObject *obj=NULL;QString serialPortPluginPath("/usr/lib/libEWhalesSerialPort.so");QPluginLoader pluginLoader(serialPortPluginPath);obj=pluginLoader.instance();if(obj!=NULL){serialPort=qobject_cast<ISerialPort *>(obj);if(serialPort){qDebug()<<serialPortPluginPath<<"is loaded...";return true;}}else{qDebug()<<serialPortPluginPath<<"is loaded failed: "<<pluginLoader.errorString();return false;}
}

特别说明

绑定信号与槽

插件中如果有信号,可以在加载插件的时候绑定对应的槽函数。但是由于插件的Class类型并不被Qt的Connect直接支持,因此需要采用以下的写法才能避免报错(使用<QObject *>类型):

 QObject *obj = NULL;QString serialPortPluginPath("/usr/lib/libEWhalesSerialPort.so");QPluginLoader pluginLoader(modbusRTUMasterPluginPath);obj = pluginLoader.instance();/* 省略无关代码 */if(obj != NULL){modbusMaster = qobject_cast<IModbusRTUMaster *>(obj);if(modbusMaster){connect(obj, SIGNAL(Get_IRData_Failed(QString)), this, SLOT(userslots_modbusMaster_GetIRDataFailed(QString)));connect(obj, SIGNAL(Get_DRData_Failed(QString)), this, SLOT(userslots_modbusMaster_GetDRDataFailed(QString)));/* 省略无关代码 */return true;}}/* 省略无关代码 */

发射自定类型变量

在插件的信号中发射自定类型的变量与在支持Qt特性的class中定义、发射自定义类型变量本质上是一样的。但是由于插件的接口文件定义的是一个普通的class类型(无构造/析构函数、无Q_OBJECT宏),因此需要在实现插件接口纯虚函数定义的class中(一般在构造函数中)完成对变量类型的注册。

一个完整的示例如下:

  • 声明类型。完全定义变量类型后,在”global scope”中调用Q_DECLARE_METATYPE完成Qt元类型的声明。
/*** IModbusRTUMaster的接口文件,接口需要定义成纯虚函数*/
class IModbusRTUMaster: public IPluginBase
{public:// 定义新的枚举变量:MBStatetypedef enum{state_disconnected = 0,state_connected,state_running,state_stop,state_notExist,state_error,} MBState;// 插件的接口virtual PluginInfo GetPluginInfo() = 0;virtual QWidget *GetPanel(QWidget *parent = nullptr) = 0;// Override as signals.// State of MB master changedvirtual void MBStateChanged(const MBState &state) = 0;/* 省略无关内容 */
}// 声明Qt的元类型
Q_DECLARE_METATYPE(IModbusRTUMaster::MBState);/*** 为了能够在运行时查询插件是否实现给定的接口,* 我们必须使用宏Q_DECLARE_INTERFACE(),* 该宏的第一参数为接口类的名称,第二个参数是一个字符串,用于唯一标记该接口类。*/
Q_DECLARE_INTERFACE (IModbusRTUMaster, QTPLUGIN_IMODBUSRTUMASTER_IID)
  • 注册类型。在实现接口类的构造函数中调用qRegisterMetaType完成类型注册。
// 实现接口类的构造函数(源文件)
ModbusRTUMaster::ModbusRTUMaster()
{// 注册Qt元类型qRegisterMetaType<MBState>("MBState");/* 省略无关内容 */
}// 实现接口类的声明(头文件)
class ModbusRTUMaster : public QThread, public IModbusRTUMaster
{Q_OBJECTpublic:/* 省略无关内容 */signals:// 接口实现为信号void MBStateChanged(const MBState &state) override;/* 省略无关内容 */
}
  • 发射与连接信号
// 在合适的地方发射信号
void ModbusRTUMaster::StartMaster()
{/* 省略无关内容 */isModbusRun = state_running;emit MBStateChanged(isModbusRun);/* 省略无关内容 */
}// 在合适的地方定义槽函数
void Panel::on_MBStateChanged(const IModbusRTUMaster::MBState &state)
{switch (state){case IModbusRTUMaster::state_disconnected:case IModbusRTUMaster::state_stop:case IModbusRTUMaster::state_notExist:case IModbusRTUMaster::state_error:on_MBStateChanged_disconnected();break;case IModbusRTUMaster::state_running:case IModbusRTUMaster::state_connected:on_MBStateChanged_Connected();break;default:break;}
}// 在合适的地方连接信号与槽函数
Panel::Panel(ModbusRTUMaster *modbusMaster, QWidget *parent) :QWidget(parent),ui(new Ui::Panel),_modbusMaster(modbusMaster)
{/* 省略无关内容 */connect(_modbusMaster,SIGNAL(SendRawData(const QByteArray&)), this, SLOT(on_MBSendRawData(const QByteArray&)));connect(_modbusMaster,SIGNAL(RecvRawData(const QByteArray&)), this, SLOT(on_MBRecvRawData(const QByteArray&)));// 绑定信号与槽connect(_modbusMaster,SIGNAL(MBStateChanged(const IModbusRTUMaster::MBState &)), this, SLOT(on_MBStateChanged(const IModbusRTUMaster::MBState &)));/* 省略无关内容 */
}

注册回调函数

普通的C++成员函数都隐含了一个“this”指针参数,当在类的非静态成员函数中访问类的非静态成员时,C++编译器通过传递一个指向对象本身的指针给其成员函数,从而能够访问类的数据成员。也就是说,即使你没有写上this指针,编译器在编译的时候自动加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。正是由于this指针的作用,使得将一个CALLBACK型的成员函数作为回调函数时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数匹配失败。所以为了实现回调,类中的成员函数必须舍弃掉隐藏的this指针参数。参考文章

下面示例展示了一种如何正确设置回调函数的方法:

#include "serialport.framework.h"
#include "serialport.core.h"
#include "serialport.interface.h"
#include "serialport.pannel.h"
#include <QApplication>
#include <QDebug>
#include <functional>
#include <stdio.h>using namespace std;
using namespace std::placeholders;SerialPortInteractive *frame1;void Widget::test_DataReceivedHandler(const unsigned char *data,int count)
{QString qString=QByteArray((const char*)data,count);qDebug()<<"received data:"<<qString;
}void Widget::test_PortChangedHandler(QList<QString> list)
{qDebug()<<"test_PortChangedHandler is called, port list is:";QListIterator<QString> hashIterator(list);while (hashIterator.hasNext()){qDebug()<<hashIterator.next();}
}void Widget::serialPortInit()
{/*串口参数设置*/SerialPort_Typedef serialPortSetting;serialPortSetting.baudRate=SerialPort_BR_19200;serialPortSetting.dataBit=SerialPort_DB_8;serialPortSetting.parity=SerialPort_CB_None;serialPortSetting.stopBit=SerialPort_SB_1;serialPortSetting.name = "/dev/ttymxc1";/*serialPort是插件加载后的实例*/serialPort->SetSerialPort(&serialPortSetting);/*std::bind函数将可调用对象和可调用对象的参数进行绑定,返回新的可调用对象(std::function类型,参数列表可能改变),返回的新的std::function可调用对象的参数列表根据bind函数实参中std::placeholders::_x从小到大对应的参数确定。*/fDataReceive=std::bind(&Widget::test_DataReceivedHandler,this,_1,_2);fPortChange=std::bind(&Widget::test_PortChangedHandler,this,_1);/*设置回调函数,用来处理串口插拔与串口数据接收。*/serialPort->SetDataReceivedHandler(fDataReceive);serialPort->SetPortChangedHandler(fPortChange);serialPort->OpenSerialPort();serialPort->StartListening();
}

Qt Plugin插件开发指南(1)- 一般开发流程相关推荐

  1. linux下软件多语言开发,Qt,多语言软件,开发流程【总结】

    环境 系统版本:ubuntu Qt版本:Qt 5.12 开始 步骤1:先编写一段Qt代码 如下,这个意思就是创建一个Qlabel.其内容 要写成 tr("hello111"),tr ...

  2. Qt,多语言软件,开发流程,总结

    环境 系统版本:ubuntu Qt版本:Qt 5.12 开始 步骤1:先编写一段Qt代码 如下,这个意思就是创建一个Qlabel.其内容 要写成 tr("hello111"),tr ...

  3. 金蝶扩展报表开发指南(基本开发流程)

    一.扩展报表菜单 1-1.进入扩展报表工具,路径如下图: 1-2.打开之后,可以看到如下界面: 扩展报表的制作主要分为两部分: 一个是数据集,数据集是去查询数据库返回来的数据,比如基础资料或者单据的一 ...

  4. Flutter Plugin开发流程

    这篇文章主要介绍了Flutter Plugin开发流程,包括如何利用Android Studio开发以及发布等. 本文主要给大家介绍如何开发Flutter Plugin中Android的部分.有关Fl ...

  5. 中国电信CTWing物联网平台接入指南(一)之开发流程

    近期研究了下电信NB设备接入平台这一块,整理下电信的物联网平台的接入开发的流程,仅作为经验分享,流程梳理之用,不足之处欢迎指正. 中国电信CTWing物联网平台接入指南(一)之开发流程 中国电信CTW ...

  6. AnyChat开发流程指南

    下面列出AnyChat Platform Core SDK基本开发流程,适用于开发视频会议系统.语音视频聊天系统.远程教育平台以及即时通讯平台(IM)等. 一.初始化 该部分是首先要完成的,用于设置S ...

  7. 视频会议开发流程指南

    下面主要介绍BR_Chat Platform Core SDK基本开发流程,适用于开发视频会议系统.语音视频聊天系统.远程教育平台以及即时通讯平台(IM)等. 一.初始化 该部分是首先要完成的,用于设 ...

  8. 武装你的小程序——开发流程指南

    前端工程本质上是软件工程的一种.软件工程化关注的是性能.稳定性.可用性.可维护性等方面,注重基本的开发效率.运行效率的同时,思考维护效率.一切以这些为目标的工作都是"前端工程化". ...

  9. rknn3399pro 2小时入门指南(一)基础概念和基本开发流程

    目录 1.开发流程 2.环境安装 3.模型转化 4.模型推理 5.应用开发 本人的开发环境大致如下: PC: Ubuntu1604lts board: rknn3399pro frame: keras ...

最新文章

  1. C++内存管理与分配方式
  2. 不擅演讲的马化腾在 08 年讲了什么?
  3. leetCode 28. Implement strStr() 字符串
  4. 程序员效率:职业倦怠的理解
  5. 基于CSS3实现闪光条效果
  6. 一个简单示例 利用jawin完成调用window中dll的调用
  7. 设计模式学习---(1)简介
  8. 北师大高级程序c语言,北师大《高级程序设计C语言》.doc
  9. python的书籍推荐_python 书籍推荐
  10. 中国城市乞丐的五大经典表情
  11. 嵌入式系统开发笔记6:CJ/T-188 水表协议解析1
  12. Python网络之数据库
  13. weblogic安装与配置注意事项
  14. shell 的大于等于小于等
  15. 【学习笔记】STM32hal库开发入门笔记
  16. vue出现client:172 [WDS] Disconnected!
  17. Android 12 悬浮通知/横幅通知状态栏应用图标显示不全
  18. 群晖NAS搭建web服务器,并发布公网可访问 2/4
  19. 如何选择最好的研究方法?——Nvivo教程
  20. 如何学会记账,并分别统计每个月收入和支出的金额

热门文章

  1. 数据分析,主成分分析例题
  2. Spring2.5+Struts1.3.8+JPA(Hibernate实现)整合之四
  3. 计算机组装与维修技能鉴定,计算机安装调试维修员(四级)技能鉴定试题单总汇.docx...
  4. 英文版权声明_干货 | 英文自我介绍,刚说了quot;my name is ...quot;就被pass了
  5. 2G3G4G的网速平常的各是多少
  6. C语言实现TCP连接
  7. 鉴权及常见的4大鉴权方式
  8. 用JS实现人脑和计算机交互,这个是真的牛!
  9. 【类似于windows的WinRAR软件】MacOS电脑好用的压缩、解压软件都有哪些?【推荐Keka】
  10. 基于Python的Opencv 自动识别银行卡卡号系统