写在前面:

晚上应该继续完成未写完的代码,但Chrome上打开的标签实在太多了,约30个了,必须关掉一些,所以需要把自己看的整理一下然后关掉。本次主要写点MFC环境下多线程串口通信相关的东西,这包括线程创建及控制、串口同步异步操作、内存非法访问(或者说是线程同步)、线程通信、Windows消息响应过程等。

遇到问题:

项目中IO传感器通信模块之前直接写在了主线程中,UI代码和串口通信代码搅合在一起,不利于后期维护,而且有个非常严重的问题,IO通信太忙导致整个系统比较卡,特别是当系统接上超过3个摄像机之后,MFC模态对话框使用Domodal()直接无法打开,卡住了,然后用户就无法操作了,这个问题必须要解决。

解决方案:

单独开辟一个线程来处理所有的串口通信,该IO线程和主线程(负责UI部分)通信,从而更新状态,不能子线程中直接更新UI,参看《MFC最好不要在子线程中操控界面上的控件》。

具体步骤:

1.创立IO线程并完成消息响应

HANDLE hThread1 = CreateThread( NULL,0,IOControlProc,(LPVOID)(m_pCOMSerialPort),0,&m_dwIOControlThreadId );//创建IO线程
CloseHandle( hThread1 );//关闭线程句柄

  其中

CSerialPort  *m_pCOMSerialPort;//通信串口
DWORD m_dwIOControlThreadId;//线程ID

  IOControlProc线程函数

DWORD WINAPI IOControlProc(LPVOID lpParameter)
{
CSerialPort *pSerialPort = (CSerialPort*)lpParameter;
UINT_PTR nHUMITimer = 0;
UINT_PTR nIOTimer = 0;
//::SetTimer(NULL,NULL,200,(TIMERPROC)TimerProc);
nHUMITimer = ::SetTimer(NULL,TEMPHUMICOMM_TIMER,500,(TIMERPROC)OnIOTimer);//温湿度
nIOTimer =::SetTimer(NULL,IOCOMM_TIMER,100,(TIMERPROC)OnIOTimer);//IO
PIOTimerStru pHumiTimer= new IOTimerStru;
pHumiTimer->nIdEvent = TEMPHUMICOMM_TIMER;
pHumiTimer->pSerialPort = pSerialPort;
PIOTimerStru pIoTimer = new IOTimerStru;
pIoTimer->nIdEvent = IOCOMM_TIMER;
pIoTimer->pSerialPort = pSerialPort;
PIOTimerStru pIoControl = new IOTimerStru;
pIoControl->nIdEvent = TEMPHUMICOMM_TIMER + IOCOMM_TIMER;
pIoControl->pSerialPort = pSerialPort;
LPARAM lParam;
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
if (WM_TIMER == msg.message)
{
lParam = msg.lParam;//WM_TIMER回调函数的地址
if (nHUMITimer == msg.wParam)
{
msg.wParam = (WPARAM)pHumiTimer;
}
else if (nIOTimer == msg.wParam)
{
msg.wParam = (WPARAM)pIoTimer;
}
}
else if (WM_IOCOMMDATA == msg.message)
{
msg.message = WM_TIMER;
pIoControl->wParam = msg.wParam;
pIoControl->lParam = msg.lParam;
msg.wParam = (WPARAM)pIoControl;
msg.lParam = lParam;
}
DispatchMessage(&msg);
}
delete pHumiTimer;
delete pIoTimer;
delete pIoControl;
return 0;
}

  这里,IO线程有自己的消息循环队列(虽然没有窗口),参看:《子线程里如何使用定时器》,把这里的代码改成死循环的(参看《是否在子线程内使用SetTimer?》),如下

VOID CALLBACK TimerProc(          HWND hwnd,
UINT uMsg,
UINT_PTR idEvent,
DWORD dwTime
){
//do some thing
return;
} DWORD WINAPI ThreadProc(LPVOID lpParameter)
{ ::SetTimer(NULL,NULL,200,(TIMERPROC)TimerProc);
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0; }

  我的消息响应函数写成如下

VOID CALLBACK OnIOTimer(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime)
{
PIOTimerStru pStru = (PIOTimerStru)idEvent;
if (pStru->nIdEvent == TEMPHUMICOMM_TIMER)//温湿度模块
{
static bool bFlag = true;
if (bFlag)
{
IOSendData(pStru->pSerialPort,2,0x0A,0,0x03);
}
else
{
IOSendData(pStru->pSerialPort,2,0x0A,0,0x04);
}
bFlag = !bFlag;
}
else if (pStru->nIdEvent == IOCOMM_TIMER)//IO控制模块
{
IOSendData(pStru->pSerialPort,1,0x10,0,0x03);
}
else if (pStru->nIdEvent == TEMPHUMICOMM_TIMER + IOCOMM_TIMER)//别的线程发送的指令
{
WORD highDeviceIndex = HIWORD(pStru->wParam);//设备号
WORD lowPortIndex = LOWORD(pStru->wParam);//设备1端口号,设备2指定温度或湿度模块
WORD highParam = HIWORD(pStru->lParam);//设备1指定状态1或者0,设备2指定整数部分
WORD lowParam = LOWORD(pStru->lParam);//设备1为0,设备2指定小数部分 IOSendData(pStru->pSerialPort,(int)highDeviceIndex,(BYTE)highParam,(BYTE)lowParam,(BYTE)lowPortIndex);
}
return;
}

  这里要注意一个问题,OnIOTimer中获得的idEvent并不是我们设置的IOCOMM_TIMER或者TEMPHUMICOMM_TIMER,究其原因,参看《SetTimer在无窗口和有窗口线程的使用》,文中关于原因的解释为“注:只有当hWnd参数为非空时,计时器的ID为设置的 nIDEvent, 系统为你自动生成一个计时器ID,可由返回时值获取.”,可由MSDN得知。

那么我们的这个IOCOMM_TIMER或者TEMPHUMICOMM_TIMER,怎么传递过去呢?查看《怎么往SetTimer的回调函数传递参数》得知,我们可由msg.wParam传递。

那么对于我们自定义的消息TEMPHUMICOMM_TIMER + IOCOMM_TIMER,我也想让OnIOTimer来处理怎么办?直接修改msg.message = WM_TIMER;就可以了嘛?答案是不行的!

为什么呢?参看《消息循环中的TranslateMessage函数和DispatchMessage函数》,原来“如果参数lpmsg指向一个WM_TIMER消息,并且WM_TIMER消息的参数IParam不为NULL,则调用IParam指向的函数,而不是调用窗口程序。”,那么我们直接修改msg.lParam为OnIOTimer函数的地址就行了。这样,所有消息响应完成了。

2.多线程访问冲突(线程冲突)
遇到一个问题,系统有一个串口通信端口,IO线程直接使用了,然后我在主线程中也发送数据,然后问题出现了,有时候会出现非法访问,跟踪了一下,原来两个线程使用了同一个串口通信缓冲区,主线程往里面压入数据的时候,可能子线程已经释放了该缓冲区,查询文章《CSerialPort连续发送大量数据时出错原因分析》,而我使用的CSerialPort是同步的,我尝试修改类库代码,报错太多,此方法放弃。最终,我的解决方法是把所有的串口IO通信全部交给子线程来做,那么主线程要做的事,可以通过发送消息给子线程,再由子线程代劳,主线程怎么发送消息给子线程?使用API函数PostThreadMessage来完成,第一个参数就是子线程的Id。

子线程收到数据后,进行验证,验证通过后,发消息给主线程,通知它更新界面。《主线程与子线程间通信解决办法 - VC/MFC

3.主线程退出前,关闭子线程

PostThreadMessage(m_dwIOControlThreadId,WM_QUIT,0,0);
Sleep(100);//需要等待关闭掉

GetMessage有消息时且消息不为WM_QUIT时返回TRUE,如果有消息且为WM_QUIT则返回FALSE,没有消息时不返回。  

这里可参看《如何正确的关闭 MFC 线程》和《GetMessage和PeekMessage的联系与区别以及用法 TranslateMessage与DispatchMessage

至此,经测试问题全部解决,记录一下。

多线程串口通信 MFC CSerialPort的更多相关文章

  1. 多线程串口编程工具CserialPort类(附VC基于MFC单文档协议通讯源程序及详细编程步骤)

    老有人觉得MSComm通讯控件很土,更有人大声疾呼:忘了它吧.确实当我们对串口编程有了一定的了解后,应该用API函数写一个属于自己的串口程序,由于编程者对程序了解,对程序修改自如.但我一直没有停止过用 ...

  2. Linux 多线程串口通信

    大概流程就是打开一个串口.然后进行串口设置.开启二个线程,一个线程写数据,另一个线程读数据. 代码如下: #include <stdio.h> #include <stdlib.h& ...

  3. Qt 串口通信 高速发送出错的解决方法总结

    使用网上的qextserialport-1.2类,自行开发多线程串口通信.开发的过程中,出现两个问题:   问题1:我用信号槽跨线程调用串口类MyCom 发送和接收数据,中间运行的时候,会内存错误,Q ...

  4. 多线程CSerialPort类的多串口通信实现

    多线程CSerialPort类的多串口通信实现  工作了之后才发现,之前在学校里真是狭隘封闭.坐井观天,拿之前发表的论文来说,工作后接触到了底层的串口.网口开发,对线程(也叫任务).操作系统时间片轮流 ...

  5. mfc 调用Windows的API函数实现同步异步串口通信(源码)

    在工业控制中,工控机(一般都基于Windows平台)经常需要与智能仪表通过串口进行通信.串口通信方便易行,应用广泛. 一般情况下,工控机和各智能仪表通过RS485总线进行通信.RS485的通信方式是半 ...

  6. VS2008基于对话框的MFC上位机串口通信(C++实现)简单例程

    首先,在 vs2008 环境下创建 MFC 运用程序 设置项目名称为 ComTest(这个地方随意命名,根据个人习惯),点击确定后,点击下一步 出现如下界面 选择"基于对话框"模式 ...

  7. (转载)用vs2010开发基于VC++的MFC 串口通信一*****两台电脑同一个串口号之间的通信

    此文章以visual C++数据採集与串口通信測控应用实战为參考教程 此文章适合VC++串口通信入门 一.页面布局及加入控件 1, 安装好vs2010如图 2, 新建一个基于VC++的MFC项目com ...

  8. Csharp Winfrom 多串口通信

    Csharp 多串口通信 顾名思义,多串口通信,普通的PC机一般只有一个串口,现在很多家用的PC都没有串口,那么问题来了,如何保证多串口呢? 有一种神器,MOXA CP-168U Series PCI ...

  9. Java串口通信具体解释

    序言 说到开源,恐怕非常少有人不挑大指称赞.学生通过开源码学到了知识,程序猿通过开源类库获得了别人的成功经验及可以按时完毕手头的project,商家通过开源软件赚到了钱……,总之是皆大欢喜.然而开源软 ...

随机推荐

  1. jdbc代码

    1.jdbcutiul的代码, package gz.itcast.util; import java.io.InputStream; import java.sql.Connection; impo ...

  2. 14 vue学习 postcssrc eslintrc.js babelrc

    一  .postcssrc.js 众所周知为兼容所有浏览器,有的CSS属性需要对不同的浏览器加上前缀,然而有时添加一条属性,需要添加3~4条类似的属性只是为了满足浏览器的兼容,这不仅会增加许多的工作量 ...

  3. gitea (git服务器), 修改配置,更换IP地址

    使用的gitea项目管理git 服务器 (可以不用备份项目, 通过直接修改gitea配置, 直接使用) 步骤1 可以直接访问项目, 步骤2 ,如果已有项目IP地址固定为192.168.1.x, 新的I ...

  4. ajax方法data参数用法的总结

    源文件分析: data的传递格式有两种:一是url字符串格式:一种是Json格式,格式分别如上 区别是:当传递的参数中包含 特殊字符如:&时,服务器解析这个参数时就会出错,而必须用encode ...

  5. linux下使用c判断文件夹是否为空的小程序

    自己写了一个 判断文件夹是否为空的小代码 //文件夹操作相关的函数的帮助$: man 3 readdir #include <stdio.h> #include <sys/types ...

  6. Apache Shiro知识点总览

    名词解释 权限认证 授权 ini文件配置 jsp标签授权 Shiro会话机制 自定义Realm 加密.解密 特性 与spring整合 名词解释 Subject:认证主体 Reaml:认证来源[jdbc ...

  7. exporting

    exporting: { buttons: { contextButton: { menuItems: [{ text: '导出png图片 100宽度', onclick: function () { ...

  8. MS SQL Server的COALESCE函数

    MS SQL Server的COALESCE函数是从一系列表达式中返回第一个NOT NULL的值. 检查[B],[Q],[S],[T],[U]的值: 检查顺序[B]->[Q]->[S]-& ...

  9. 判断ip地址是否为内网ip或局域网ip

    bool IsLanIp(string& ip) { ,) == ,) == ,) == "192.") { return true; } else { return fa ...

  10. 杭电1002_A + B Problem II

    这是该题的链接http://acm.hdu.edu.cn/showproblem.php?pid=1002 具体的题的内容就不过多描述了,想必你已经知道了,当我看完这道题后就知道咋写了,可是这道题从开 ...