Qt Plugin插件开发指南

Qt插件开发指南

Author: 王涛

Date: 2020.02.17

版本: V1.1

概述

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()**来判断一个插件是否实现了接口。

Ref. 参考文章

插件的开发

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

  • 声明接口文件

接口文件在插件开发、插件调用中都需要引用。接口的方法需要定义成纯虚函数。

#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;
};

/*
为了能够在运行时查询插件是否实现给定的接口,我们必须使用宏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 <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 >= 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 <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);
}

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

猜你喜欢

转载自blog.csdn.net/u013441358/article/details/104361368