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

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

一共定义了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的事件循环仅对运行该事件循环的线程中产生的事件有效,没有在该线程产生的事件是无法传递的。
    2. 对象线程和函数运行线程
    对象线程是指在创建该对象的线程函数运行线程指运行该函数的线程。为了使SerialPort正常运行,要保证SerialPort对象的操作函数在事件循环所在的线程运行。但是由于DLL导出函数运行在主线程,而事件循环运行在子线程,需要通过Connect函数实现跨线程调用。

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

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

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

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

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

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

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

定义了4个槽函数:

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

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

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

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

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

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


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

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


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


















  • 5
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 25
    评论
/*====================================================================================================== =======================================================================================================*/ /*======================================================================================================== 如何使用? 1.引入DLL文件,引入头本文件,就已声明了导入函数 //要引入DLL的方法 //1、把SERIALPORTDLL1.dllSERIALPORTDLL1.lib拷贝到工程目录下 //2、然后在 工程->设置->连接->分类->常规->对象/模块输入:SERIALPORTDLL1.lib 2.调用Init初始化串口 3.调用SetReceiveFuntion(&ReveveChar);指定接收和处理数据的函数 4.实现void ReveveChar(WPARAM data, LPARAM port)这个函数 5.调用SendData发送数据 6.ClosePort(); ========================================================================================================*/ /*======================================================================================================= 函 数 名: Init 功 能: 打开并初始化串口 参数说明: UINT port //端口号 UINT baud //波特率 char parity = 'N' //校验位,默认为无校验位 UINT databits = 8 //数据位,一个字节的位数,默认为8位 UINT stopbit = 1 //停止位,默认为1位 使用例子: Init(2,9600); //打开串口2,波特率9600,其它值为默认值(无奇偶校验、数据位为8、停止位为1) Init(4,4800,'N',10);//打开串口4,波特率为4800,无奇偶校验,数据位为10,(停止位默认为1) ... ... ======================================================================================================*/ extern "C" __declspec(dllimport) void Init( UINT port, //端口号 UINT baud, //波特率 char parity = 'N', //校验位 UINT databits=8 , //数据位 UINT stopbit=1 //停止位 ); /*========================================================================================================== 函 数 名: SendData 功 能: 向串口发送数据,使用之前必须先调用Init函数初始化串口 参数说明: char data[] //要发送的数据 int datalen //发送数据的长度 使用例子: 例1: char a[] = {0x01,0x03,0x00,0x03,0x00,0x02,0x34,0x0B};//准备要发送的十六进制:01 03 00 03 00 02 34 0B SendData(a,8); //向串口发送十六进制数:01 03 00 03 00 02 34 0B 例2: char a[] = "$01001000020110*" //准备要发送的字符串$01001000020110* SendData(a,16); //向串口发送:$01001000020110* ============================================================================================================*/ extern "C" __declspec(dllimport) void SendData( char data[], int datalen ); /*=================================================================================================================== 函 数 名: SetReceiveFuntion 功 能: 指定接收数据的函数,指定好后,串口接收缓冲区每有一个字节的数据时会自动触发和调用所指定的函数 参数说明: a、参数需要一个函数指针, b、该指针指向一个用于接收数据的函数(该函数由用户自己声明、定义和实现), c、用户定义该函数时必须要满足一下条件: 1、该函数为全局函数 2、返回值为void 3、形参为两个,类型为:形参为UINT,LONG 使用例子: 1、void ReveveChar(WPARAM data, LPARAM port);//声明一个用于接收数据的全局函数,本头文件已默认声明这个函数,用户无需再次声明 2、 CString strTemp; void ReveveChar(WPARAM data, LPARAM port)//实现这个接收数据并处理数据的函数 { static int RecevCount = 0; //用于接收字节数的计数 char str[512] = ""; sprintf(str,"%02x",data); //接收十六进制数,并格式化为字符形式 strTemp += str; RecevCount++; if (RecevCount >= 8) //接收到数据长度等于了协议长度,则开始进行协议的处理 { RecevCount = 0; strTemp = "接收到得数据协议为" + strTemp; AfxMessageBox(strTemp); strTemp = ""; } } 3、SetReceiveFuntion(&ReveveChar);//指定void ReveveChar(WPARAM data, LPARAM port);该函数接收和处理数据 =========================================================================================================================*/ extern "C" __declspec(dllimport) void SetReceiveFuntion(void (*pfCallBack)(WPARAM data, LPARAM port)); /*================================================= 函 数 名: ClosePort 功 能: 关闭由Init函数打开的串口 参数说明: 无需参数 调用例子: ClosePort(); =================================================*/ extern "C" __declspec(dllimport) void ClosePort(); /*=============================================================================================================== 函 数 名: ReveveChar 功 能: 每当串口接收到一个字符(字节)时就会自动触发和调用这个函数, 用户只需实现不必也不能去亲自调用, 注意:打开串口后,必须要调用SetReceiveFuntion(ReveveChar) 参数说明: data //接收到的是串口中的一个字节数据 port //端口号,表明是哪个端口接收到的数据 调用例子: 只要实现,无需自己调用。 实现处理数据可参考如下简单的例子,思想是边接收数据边处理 void ReveveChar(WPARAM data, LPARAM port) { static int RecevCount = 0; //用于接收字节数的计数 char str[512] = ""; sprintf(str,"%02x",data); //接收十六进制数,并格式化为字符形式 strTemp += str; RecevCount++; if (RecevCount >= 8) //接收到数据长度等于了协议长度,则开始进行协议的处理 { RecevCount = 0; strTemp = "接收到得数据协议为" + strTemp; AfxMessageBox(strTemp); strTemp = ""; } } 说明:本函数并非为导出函数,写在这里仅仅是起到声明一个函数的作用,用户也可以删除掉,自己声明和实现! ===============================================================================================================*/ void ReveveChar(WPARAM data, LPARAM port);
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值