QT仪器通信库——串口(SerialPort)的DLL封装

    最近想要做一个标准的仪器通信库(包括SerialPort、TCP、UDP、USB等),考虑通信库的通用性,决定采用可以跨平台的QT封装。
    QT 5.0以上提供了串口通信库即QSerialPort和QSerialPortInfo两个串口类。QSerialPort提供操作RS232的相关函数接口,QSerialPortInfo提供相关串口的信息。
    一、函数接口介绍
    为了DLL通用性,DLL的导出函数采用C接口导出,定义了串口操作的基本函数接口,如下图所示:
QT仪器通信库——串口(SerialPort)的DLL封装_第1张图片
一共定义了5个函数,即Open、Close、Write、InstallCallBack、UnInit。

函数名

参数

Open
打开

type

通信协议类型(SerialPortTCPUDPUSB等)

arg

通信协议类型的相关参数

Close
关闭
handle
句柄
Write
handle
句柄
data
待写入数据
maxSize
待写入数量
InstallCallBack
读函数注册
handle
句柄
callback
数据读回调函数指针
UnInit
事件循环停止
    二、QSerialPort类对象与handle句柄转换
    为了实现类对象与int类型的handle句柄转换,定义了一个以handle为索引的map表,建立类对象与handle的映射,如下图所示:

    并且定义了一个GetClass函数,实现通过handle查找对应的索引,返回类对象的功能。

    文中的CommuLib是一个封装了各种通信协议,并提供统一接口的类。上文提到的DLL导出的4个函数就是调用CommuLib类中的基本函数接口。
    三、QT事件循环的创建与运行
    由于QT中QSerialPort类内部涉及到了信号与槽,信号与槽功能依赖QT的事件循环,如果事件循环没有运行,当外部程序调用write函数时,会提示QObject::startTimer: Timers can only be used with threads started with QThread.错误。所以QT创建的DLL需要做一些额外的工作。
    1. QCoreApplication类介绍
    该类的功能是:The QCoreApplication class provides an event loop for Qt applications without UI.即为非GUI应用程序提供事件循环。
    主要函数有:
    int QCoreApplication::exec() 进入主事件循环,直到 exit() 被调用后退出。
    void QCoreApplication::exit(int returnCode = 0) 通知exec()函数退出。
    void QCoreApplication::quit() 通知exec()函数退出,并且返回0(成功)。
    由于exec() 运行后会一直阻塞,直到exit() 被调用,因此需要开辟线程运行事件循环。重写run函数:
QT仪器通信库——串口(SerialPort)的DLL封装_第2张图片
    如果此时,我们认为万事大吉,可以收工了,那就大错特错了。理由是什么呢?那是因为QT的事件循环仅对运行该事件循环的线程中产生的事件有效,没有在该线程产生的事件是无法传递的。
    2. 对象线程和函数运行线程
    对象线程是指在创建该对象的线程函数运行线程指运行该函数的线程。为了使SerialPort正常运行,要保证SerialPort对象的操作函数在事件循环所在的线程运行。但是由于DLL导出函数运行在主线程,而事件循环运行在子线程,需要通过Connect函数实现跨线程调用。

    QObject的connect函数有几种连接方式,

      a) DirectConnection,信号发送后槽函数立即执行,由sender的所在线程执行,同步;

      b) QueuedConnection,信号发送后返回,相关槽函数由receiver所在的线程在返回到事件循环后执行,异步;

      c) 默认使用的是Qt::AutoConnection,当sender和receiver在同一个线程内时,采用DirectConnection的方式,当sender和receiver在不同的线程时,采用QueuedConnection的方式。

    定义CommunicationLib类,类对象在子线程中创建,定义如下所示:

QT仪器通信库——串口(SerialPort)的DLL封装_第3张图片

定义CommuLib类型指针,指向创建的SerialPort对象。

定义QCoreApplication类型指针,在CLStop函数中调用a->quit()函数,停止exec()函数。

定义了4个槽函数:

CLopen功能:SerialPort打开函数,运行在子线程。

QT仪器通信库——串口(SerialPort)的DLL封装_第4张图片

CLwrite功能:SerialPort写函数,运行在子线程。

QT仪器通信库——串口(SerialPort)的DLL封装_第5张图片

CLclose功能:SerialPort关闭函数,运行在子线程。

QT仪器通信库——串口(SerialPort)的DLL封装_第6张图片

CLStop功能:SerialPort事件循环停止函数,运行在子线程。

QT仪器通信库——串口(SerialPort)的DLL封装_第7张图片

AppThread类为子线程类,在主线程创建,定义了4个信号函数,定义如下:

QT仪器通信库——串口(SerialPort)的DLL封装_第8张图片

    4个信号函数和槽函数通过Connect函数中的QueuedConnection连接方式连接,在run函数中利用Connect函数连接DLL导出函数和子线程函数。


四、DLL生成。
    生成DLL时,如果采用Debug模式,里面有Qdebug()函数打印,用VS调用该DLL后,会提示内存泄漏。如果采用Release模式,则不会提示内存泄漏,推测可能与Qdebug()函数有关。

注意:
本文生成的dll,已采用mfc调用dll验证。


源程序下载地址:https://github.com/wellfrogliu/Qt.git


















你可能感兴趣的:(vc++,Qt)