zoukankan      html  css  js  c++  java
  • 多线程串口通信 MFC CSerialPort

    写在前面:

             晚上应该继续完成未写完的代码,但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 

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

  • 相关阅读:
    HAproxy 1.5 dev14 发布
    IBM/DW 使用 Java 测试网络连通性的几种方法
    Skype 4.1 Linux 发布,支持微软帐号登录
    Dorado 7.1.20 发布,Ajax的Web开发平台
    Aspose.Slides for Java 3.0 发布
    开发版本 Wine 1.5.18 发布
    BitNami Rubystack 开始支持 Ruby 2.0
    XWiki 4.3 正式版发布
    Silverlight实例教程 Out of Browser的Debug和Notifications窗口
    Silverlight实例教程 Out of Browser与Office的互操作
  • 原文地址:https://www.cnblogs.com/xingrun/p/3587144.html
Copyright © 2011-2022 走看看