多线程串口通信 MFC CSerialPort

原文转载自http://www.cnblogs.com/xingrun/p/3587144.html?utm_source=tuicool&utm_medium=referral。


以下全为转载:

写在前面:

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

遇到问题:

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

解决方案:

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

具体步骤:

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

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

  其中

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

  IOControlProc线程函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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?》),如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;
     
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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.主线程退出前,关闭子线程

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

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

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

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


  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值