Author: 王涛
Date: 2020.02.17
版本: V1.1
插件是一种遵循一定规范的应用程序接口编写出来的程序,定位于开发实现应用软件平台不具备的功能的程序。插件与宿主程序之间通过接口联系,就像硬件插卡一样,可以被随时删除,插入和修改,所以结构很灵活,容易修改,方便软件的升级和维护。Qt提供了两种API用于创建插件:一种是高阶API,用于扩展Qt本身的功能,如自定义数据库驱动,图像格式,文本编码,自定义样式等;一种是低阶API,用于扩展Qt应用程序。本文主要是通过低阶API来创建Qt插件,并通过静态、动态两种方式来调用插件。
Ref. 参考文章
Qt插件开发主要包括 声明接口文件、建立工程文件、声明和定义实现接口的类等步骤。
接口文件在插件开发、插件调用中都需要引用。接口的方法需要定义成纯虚函数。
#ifndef ISERIALPORT_H
#define ISERIALPORT_H
#include
#include
#include
#include
#include
/*
宏定义 接口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;
};
/*
为了能够在运行时查询插件是否实现给定的接口,我们必须使用宏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 += widgets
QMAKE_CXXFLAGS += -std=c++11
TARGET = EWhalesSerialPort
# TEMPLATE = lib 生成插件
# TEMPLATE = app 生成应用程序
TEMPLATE = lib
CONFIG += plugin
SOURCES += main.cpp\
serialport.core.cpp \
serialport.pannel.cpp \
serialport.framework.cpp \
serialport.thread.cpp
HEADERS += \
serialport.core.h \
serialport.interface.h \
serialport.pannel.h \
serialport.framework.h \
serialport.thread.h
FORMS += widget.ui
unix {
#target.path += /root/
target.path =/usr/lib
INSTALLS += target
}
#target.path += /root/
#INSTALLS += target
RESOURCES += \
resource.qrc
#ifndef SERIALPORTINTERACTIVE_H
#define SERIALPORTINTERACTIVE_H
#include
#include
#include
#include
#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 >= 0x050000
Q_PLUGIN_METADATA(IID QTPLUGIN_ISERIALPORT_IID)
#endif
public:
/*
此处省略了与插件开发无关的代码
*/
SerialPortInteractive();
~SerialPortInteractive();
void GetPortList(QStringList &portList);
void GetSerialPortConfig(SerialPort_Typedef &serialPort_Typedef);
void SetSerialPort(SerialPort_Typedef *serialPort_Typedef);
int OpenSerialPort();
int CloseSerialPort();
void StartListening();
void StopListening();
int SendData(QByteArray data);
int SendData(QString string);
void SetPortChangedHandler(FUNportChange fPortChange);
void SetDataReceivedHandler(FUNdataReceive fDataRecv);
QWidget* GetPanel();
void ShowPanel(QWidget *parent);
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);
};
#endif // SERIALPORT_FRAMEWORK_H
#include "serialport.framework.h"
#include
#include
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);
}
void SerialPortInteractive::_receiveData(const unsigned char *data,int count)
{
if(_fdatarecv!=NULL)
_fdatarecv(data,count);
}
/*
Qt4与Qt5的插件开发方式略有差异,此处采用条件编译可以实现版本兼容。
导出Qt插件,第一参数为插件的IID,第二个参数为实现接口的类。
*/
#if QT_VERSION < 0x050000
Q_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;
}
}
普通的C++成员函数都隐含了一个“this”指针参数,当在类的非静态成员函数中访问类的非静态成员时,C++编译器通过传递一个指向对象本身的指针给其成员函数,从而能够访问类的数据成员。也就是说,即使你没有写上this指针,编译器在编译的时候自动加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。正是由于this指针的作用,使得将一个CALLBACK型的成员函数作为回调函数时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数匹配失败。所以为了实现回调,类中的成员函数必须舍弃掉隐藏的this指针参数。Ref. 参考文章
下面示例展示了一种如何正确设置回调函数的方法:
#include "serialport.framework.h"
#include "serialport.core.h"
#include "serialport.interface.h"
#include "serialport.pannel.h"
#include
#include
#include
#include
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();
}