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

zz/2023/10/1 3:51:06

版本记录表

文章目录

  • 概述
    • 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();
}

http://www.ngui.cc/zz/1918319.html

相关文章

人工智能+软件测试 编程程Python自动化全能型人才打造

人工智能近几年来飞速发展&#xff0c;而且所到之处&#xff0c;均有些杀伤力&#xff0c;那么你觉得未来不懂人工智能的测试员会被淘汰吗? 近年来科技飞速迭代&#xff0c;从云计算、大数据再到人工智能、区块链&#xff0c;紧跟潮流、选对方向才能成为新时代的弄潮儿。各个…

系统上线后是运维流程还是开发过程?

一位朋友提到这样的问题&#xff1a; 软件系统上线后是运维流程还是开发过程&#xff0c;需求变更量较大的情况。俺的简单建议&#xff1a; 这个不是二选一的问题&#xff0c;系统上线后&#xff0c;其实维护和开发的工作都会有。你提到“需求变更量较大的情况”&#xff0c;那…

IT软件开发小白进阶路线

前提&#xff1a;本文是结合大牛自己补充的技术路线&#xff0c;仅供参考。 一、技术路线介绍 本节只介绍纯IT领域的技术路线&#xff0c;而对于更高级的技术&#xff08;例如计算机视觉、机器学习、人工智能等等&#xff09;不予考虑。 技术路线是一条通往该行业的道路&#x…

整个项目流程中测试团队究竟该做哪些事情和承担了一个怎样的角色?

文章目录前言项目整个阶段一些规范说明前言 当前 IT 公司一般拥有四大角色 产品&#xff08;业务&#xff09;开发&#xff08;Web&#xff0c;App&#xff09;测试&#xff08;测开&#xff0c;自动化&#xff0c;手工&#xff09;运维 大型的 IT 企业拥有着强大 QA 团队和…

Scrum敏捷开发笔记

1. 极限编程&#xff08;XP&#xff09;、Scrum、精益软件开发、动态系统开发方法&#xff08;DSDM&#xff09;、特征驱动开发、水晶开发&#xff08;Crystal Clear&#xff09; 2. 敏捷开发注重沟通&#xff0c;对需求、变更积极 3. 个体和交互重于过程和工…

软件开发:个人与团队是永远的核心

转自&#xff1a;http://blog.csdn.net/hzliyun/article/details/7561699点击打开链接注&#xff1a;本文节选自我正在创作的第二本书《C跨平台与框架开发》&#xff0c;其中一些措词并未就博文进行调整。读者阅读时请注意这一点。Brooks在他的《人月神话》中指出软件行业没有“…

对于维护型项目,是不是可不可以不用按照一般的软件开发流程走?

有朋友提到&#xff1a; 对于维护型项目&#xff0c;是不是可不可以不用按照一般的软件开发流程走&#xff0c;有没有什么更简洁高效的流程来管理呢&#xff1f;每次都有计划&#xff0c;实际情况比较多&#xff0c;比如来个紧急需求&#xff0c;比如严重bug&#xff0c;导致不…

表格类产品标签的制作

大家在逛超市的时候&#xff0c;琳琅满目的商品都有自己的专属标签&#xff0c;这个标签就是商品的身份证。标签上一般会有产品名称&#xff0c;产地、规格&#xff0c;价格等信息&#xff0c;这样的标签一般以表格形式来呈现。这篇文章就详细介绍一下如何制作此类标签。 打开软…

UGUI制作Tab标签页

方法有2种 1.利用UGUI中的Button来制作。 <pre name"code" class"csharp">using UnityEngine; using UnityEngine.UI; using System.Collections; using System; using System.Collections.Generic; [Serializable] public class TabControlEntry {…

全卷积 FCN 数据标签制作

原文&#xff1a;http://www.echojb.com/image/2017/06/06/417204.html 一 全卷积神经网络 深度学习图像分割&#xff08;FCN&#xff09;训练自己的模型大致可以以下三步&#xff1a; 1.为自己的数据制作label&#xff1b; 2.将自己的数据分为train,val和test集&#xff1b; …