• Remon Spekreijse CSerialPort串口类的修正版2014-01-10


    转自:http://m.blog.csdn.net/blog/itas109/18358297#

    2014-1-16阅读691 评论0

     如需转载请标明出处:http://blog.csdn.net/itas109

    这是一份优秀的类文件,好多的地方值得我们学习,具体在多线程,事件,自定义消息,类的封装方面等等。
    Remon提供的串口类网址为: http://codeguru.earthweb.com/network/serialport.shtml

    其他贡献者:http://blog.csdn.net/liquanhai/article/details/6941574

    代码下载:http://download.csdn.net/detail/itas109/6855323

    代码托管:https://code.csdn.net/itas109/cserialport

    源代码如下:

     CSerialPort.h

    /*
    **	FILENAME			CSerialPort.h
    **
    **	PURPOSE				This class can read, write and watch one serial port.
    **						It sends messages to its owner when something happends on the port
    **						The class creates a thread for reading and writing so the main
    **						program is not blocked.
    **
    **	CREATION DATE		15-09-1997
    **	LAST MODIFICATION	12-11-1997
    **
    **	AUTHOR				Remon Spekreijse
    **
    **
    ************************************************************************************
    **  author: mrlong date:2007-12-25
    **
    **  改进
    **    1) 增加ClosePort
    **    2) 增加 writetoProt() 两个方法
    **    3) 增加 SendData 与 RecvData 方法
    ** 
    ************************************************************************************
    **  author:liquanhai date:2011-11-04 
    **  改进 
    **    1)增加 ClosePort中交出控制权,防止死锁问题 
    **    2) 增加 ReceiveChar中防止线程死锁 
    *************************************************************************************** 
    **  author: itas109  date:2014-01-10
    **  Blog:blog.csdn.net/itas109
    **
    **  改进
    **    1) 解决COM10以上端口无法显示的问题
    **    2) 扩展可选择端口,最大值MaxSerialPortNum可以自定义
    **    3) 添加QueryKey()和Hkey2ComboBox两个方法,用于自动查询当前有效的串口号。
    ** 
    */
    
    #ifndef __SERIALPORT_H__
    #define __SERIALPORT_H__
    
    #define WM_COMM_BREAK_DETECTED		WM_USER+1	// A break was detected on input.
    #define WM_COMM_CTS_DETECTED		WM_USER+2	// The CTS (clear-to-send) signal changed state. 
    #define WM_COMM_DSR_DETECTED		WM_USER+3	// The DSR (data-set-ready) signal changed state. 
    #define WM_COMM_ERR_DETECTED		WM_USER+4	// A line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY. 
    #define WM_COMM_RING_DETECTED		WM_USER+5	// A ring indicator was detected. 
    #define WM_COMM_RLSD_DETECTED		WM_USER+6	// The RLSD (receive-line-signal-detect) signal changed state. 
    #define WM_COMM_RXCHAR				WM_USER+7	// A character was received and placed in the input buffer. 
    #define WM_COMM_RXFLAG_DETECTED		WM_USER+8	// The event character was received and placed in the input buffer.  
    #define WM_COMM_TXEMPTY_DETECTED	WM_USER+9	// The last character in the output buffer was sent.  
    
    #define MaxSerialPortNum 20   ///有效的串口总个数,不是串口的号 //add by itas109 2014-01-09
    class CSerialPort
    {														 
    public:
    
    	// contruction and destruction
    	CSerialPort();
    	virtual		~CSerialPort();
    
    	// port initialisation
    	///串口初始化
    	BOOL		InitPort(CWnd* pPortOwner, //串口句柄
    		                 UINT portnr = 1, //端口号
    		                 UINT baud = 19200, //波特率
    		                 char parity = 'N', //奇偶校验
    		                 UINT databits = 8, //数据位
    		                 UINT stopbits = 1, //停止位
    		                 DWORD dwCommEvents = EV_RXCHAR, //消息类型
    		                 UINT writebuffersize = 1024//写缓存
    						 );
    
    
    	// start/stop comm watching
    	///控制串口监视线程
    	BOOL		 StartMonitoring();//开始监听
    	BOOL		 RestartMonitoring();//重新监听
    	BOOL		 StopMonitoring();//停止监听
    
    	DWORD		 GetWriteBufferSize();///获取写缓冲大小
    	DWORD		 GetCommEvents();///获取事件
    	DCB			 GetDCB();///获取DCB
    
    	COMMTIMEOUTS GetCommTimeOuts();//
    	BOOL         SetCommTimeOuts(COMMTIMEOUTS * lpTimeOuts);
    
    	///写数据到串口
    	void		WriteToPort(char* string);
    	void		WriteToPort(char* string,int n);//add by mrlong 2007-12-25
    	void		WriteToPort(LPCTSTR string);//add by mrlong 2007-12-25
    	void		WriteToPort(LPCTSTR string,int n);//add by mrlong 2007-12-2
    	void        WriteToPort(BYTE* Buffer, int n);// add by mrlong
    	void		ClosePort();//关闭串口 // add by mrlong 2007-12-25
    
    	BOOL        RecvData(LPTSTR lpszData, const int nSize);//串口发送函数 by mrlong 2008-2-15
    	void        SendData(LPCTSTR lpszData, const int nLength);//串口接收函数 by mrlong 2008-2-15
    
    	void QueryKey(HKEY hKey);///查询注册表的串口号,将值存于数组中
    	void Hkey2ComboBox(CComboBox& m_PortNO);///将QueryKey查询到的串口号添加到CComboBox控件中
    
    protected:
    	// protected memberfunctions
    	void		ProcessErrorMessage(char* ErrorText);///错误处理
    	static UINT	CommThread(LPVOID pParam);///线程函数
    	static void	ReceiveChar(CSerialPort* port, COMSTAT comstat);///接收字符
    	static void	WriteChar(CSerialPort* port);///写字符
    	
    
    	// thread
    	CWinThread*			m_Thread;///thread监视线程
    	BOOL                m_bIsSuspened;///thread监视线程是否挂起
    
    	// synchronisation objects
    	CRITICAL_SECTION	m_csCommunicationSync;///临界资源
    	BOOL				m_bThreadAlive;///监视线程运行标志
    
    	// handles
    	HANDLE				m_hWriteEvent;
    	HANDLE				m_hComm;///串口句柄
    	HANDLE				m_hShutdownEvent;
    
    	// Event array. 
    	// One element is used for each event. There are two event handles for each port.
    	// A Write event and a receive character event which is located in the overlapped structure (m_ov.hEvent).
    	// There is a general shutdown when the port is closed.
    	///事件数组,包括一个写事件,接收事件,关闭事件
    	///一个元素用于一个事件。有两个事件线程处理端口。
    	///写事件和接收字符事件位于overlapped结构体(m_ov.hEvent)中
    	///当端口关闭时,有一个通用的关闭。
    	HANDLE				m_hEventArray[3];
    
    	// structures
    	OVERLAPPED			m_ov;///异步I/O
    	COMMTIMEOUTS		m_CommTimeouts;///超时设置
    	DCB					m_dcb;///设备控制块
    
    	// owner window
    	CWnd*				m_pOwner;
    
    	// misc
    	UINT				m_nPortNr;
    	char*				m_szWriteBuffer;///写缓冲区
    	DWORD				m_dwCommEvents;
    	DWORD				m_nWriteBufferSize;///写缓冲大小
    
    	int m_nWriteSize; //写入字节数 //add by mrlong 2007-12-25
    };
    
    #endif __SERIALPORT_H__
    
    
    

    CSerialPort.cpp

    /*
    **	FILENAME			CSerialPort.cpp
    **
    **	PURPOSE				This class can read, write and watch one serial port.
    **						It sends messages to its owner when something happends on the port
    **						The class creates a thread for reading and writing so the main
    **						program is not blocked.
    **
    **	CREATION DATE		15-09-1997
    **	LAST MODIFICATION	12-11-1997
    **
    **	AUTHOR				Remon Spekreijse
    **
    **
    */
    
    #include "stdafx.h"
    #include "SerialPort.h"
    
    #include <assert.h>
    
    int m_nComArray[20];
    //
    // Constructor
    //
    CSerialPort::CSerialPort()///构造函数
    {
    	m_hComm = NULL;
    	// initialize overlapped structure members to zero
    	///初始化异步结构体
    	m_ov.Offset = 0;
    	m_ov.OffsetHigh = 0;
    
    	// create events
    	m_ov.hEvent = NULL;
    	m_hWriteEvent = NULL;
    	m_hShutdownEvent = NULL;
    
    	m_szWriteBuffer = NULL;
    
    	m_bThreadAlive = FALSE;
    
    	m_nWriteSize=1;///
    	m_bIsSuspened = FALSE;///
    }
    
    //
    // Delete dynamic memory
    //
    CSerialPort::~CSerialPort()///析构函数
    {
    	do
    	{
    		SetEvent(m_hShutdownEvent);
    	} while (m_bThreadAlive);
    
    	
    	// if the port is still opened: close it 
    	if (m_hComm != NULL)
    	{
    		CloseHandle(m_hComm);
    		m_hComm = NULL;
    	}
    	// Close Handles  
    	if(m_hShutdownEvent!=NULL)
    		CloseHandle( m_hShutdownEvent); 
    	if(m_ov.hEvent!=NULL)
    		CloseHandle( m_ov.hEvent ); 
    	if(m_hWriteEvent!=NULL)
    		CloseHandle( m_hWriteEvent ); 
    
    	TRACE("Thread ended
    ");
    	delete [] m_szWriteBuffer;
    }
    
    //
    // Initialize the port. This can be port 1 to MaxSerialPortNum.
    ///初始化串口。只能是1-MaxSerialPortNum
    //
    BOOL CSerialPort::InitPort(CWnd* pPortOwner,	// the owner (CWnd) of the port (receives message)
    						   UINT  portnr,		// portnumber (1..MaxSerialPortNum)
    						   UINT  baud,			// baudrate
    						   char  parity,		// parity 
    						   UINT  databits,		// databits 
    						   UINT  stopbits,		// stopbits 
    						   DWORD dwCommEvents,	// EV_RXCHAR, EV_CTS etc
    						   UINT  writebuffersize)	// size to the writebuffer
    {
    	assert(portnr > 0 && portnr < MaxSerialPortNum+1);///add by itas109 2014-01-09
    	assert(pPortOwner != NULL);
    
    	// if the thread is alive: Kill
    	///如果线程存在,则关掉进程
    	if (m_bThreadAlive)
    	{
    		do
    		{
    			SetEvent(m_hShutdownEvent);
    		} while (m_bThreadAlive);
    		TRACE("Thread ended
    ");
    	}
    
    	// create events
    	if (m_ov.hEvent != NULL)
    		ResetEvent(m_ov.hEvent);
    	else
    		m_ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    
    	if (m_hWriteEvent != NULL)
    		ResetEvent(m_hWriteEvent);
    	else
    		m_hWriteEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    	
    	if (m_hShutdownEvent != NULL)
    		ResetEvent(m_hShutdownEvent);
    	else
    		m_hShutdownEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    
    	// initialize the event objects
    	///事件数组初始化,设定优先级别
    	m_hEventArray[0] = m_hShutdownEvent;	// highest priority
    	m_hEventArray[1] = m_ov.hEvent;
    	m_hEventArray[2] = m_hWriteEvent;
    
    	// initialize critical section
    	///初始化临界资源
    	InitializeCriticalSection(&m_csCommunicationSync);
    	
    	// set buffersize for writing and save the owner
    	m_pOwner = pPortOwner;
    
    	if (m_szWriteBuffer != NULL)
    		delete [] m_szWriteBuffer;
    	m_szWriteBuffer = new char[writebuffersize];
    
    	m_nPortNr = portnr;
    
    	m_nWriteBufferSize = writebuffersize;
    	m_dwCommEvents = dwCommEvents;
    
    	BOOL bResult = FALSE;
    	char *szPort = new char[50];
    	char *szBaud = new char[50];
    
    	
    	/*
    	多个线程操作相同的数据时,一般是需要按顺序访问的,否则会引导数据错乱,
    	无法控制数据,变成随机变量。为解决这个问题,就需要引入互斥变量,让每
    	个线程都按顺序地访问变量。这样就需要使用EnterCriticalSection和
    	LeaveCriticalSection函数。
    	*/
    	// now it critical!
    	///进入临界区
    	EnterCriticalSection(&m_csCommunicationSync);
    
    	// if the port is already opened: close it
    	///串口已打开就关掉
    	if (m_hComm != NULL)
    	{
    		CloseHandle(m_hComm);
    		m_hComm = NULL;
    	}
    
    	// prepare port strings
    	///串口参数
    	sprintf(szPort, "\\.\COM%d", portnr);///可以显示COM10以上端口//add by itas109 2014-01-09
    	sprintf(szBaud, "baud=%d parity=%c data=%d stop=%d", baud, parity, databits, stopbits);
    
    	// get a handle to the port
    	/*
    	通信程序在CreateFile处指定串口设备及相关的操作属性,再返回一个句柄,
    	该句柄将被用于后续的通信操作,并贯穿整个通信过程串口打开后,其属性
    	被设置为默认值,根据具体需要,通过调用GetCommState(hComm,&&dcb)读取
    	当前串口设备控制块DCB设置,修改后通过SetCommState(hComm,&&dcb)将其写
    	入。运用ReadFile()与WriteFile()这两个API函数实现串口读写操作,若为异
    	步通信方式,两函数中最后一个参数为指向OVERLAPPED结构的非空指针,在读
    	写函数返回值为FALSE的情况下,调用GetLastError()函数,返回值为ERROR_IO_PENDING,
    	表明I/O操作悬挂,即操作转入后台继续执行。此时,可以用WaitForSingleObject()
    	来等待结束信号并设置最长等待时间
    	*/
    	m_hComm = CreateFile(szPort,						// communication port string (COMX)
    					     GENERIC_READ | GENERIC_WRITE,	// read/write types
    					     0,								// comm devices must be opened with exclusive access
    					     NULL,							// no security attributes
    					     OPEN_EXISTING,					// comm devices must use OPEN_EXISTING
    					     FILE_FLAG_OVERLAPPED,			// Async I/O
    					     0);							// template must be 0 for comm devices
    
    	///创建失败
    	if (m_hComm == INVALID_HANDLE_VALUE)
    	{
    		// port not found
    		delete [] szPort;
    		delete [] szBaud;
    
    		return FALSE;
    	}
    
    	// set the timeout values
    	///设置超时
    	m_CommTimeouts.ReadIntervalTimeout = 1000;
    	m_CommTimeouts.ReadTotalTimeoutMultiplier = 1000;
    	m_CommTimeouts.ReadTotalTimeoutConstant = 1000;
    	m_CommTimeouts.WriteTotalTimeoutMultiplier = 1000;
    	m_CommTimeouts.WriteTotalTimeoutConstant = 1000;
    
    	// configure
    	///配置
    	///分别调用Windows API设置串口参数
    	if (SetCommTimeouts(m_hComm, &m_CommTimeouts))///设置超时
    	{
    		/*
    		若对端口数据的响应时间要求较严格,可采用事件驱动方式。
    		事件驱动方式通过设置事件通知,当所希望的事件发生时,Windows
    		发出该事件已发生的通知,这与DOS环境下的中断方式很相似。Windows
    	    定义了9种串口通信事件,较常用的有以下三种:
    			EV_RXCHAR:接收到一个字节,并放入输入缓冲区;
    			EV_TXEMPTY:输出缓冲区中的最后一个字符,发送出去;
    			EV_RXFLAG:接收到事件字符(DCB结构中EvtChar成员),放入输入缓冲区
    		在用SetCommMask()指定了有用的事件后,应用程序可调用WaitCommEvent()来等待事
    		件的发生。SetCommMask(hComm,0)可使WaitCommEvent()中止
    		*/
    		if (SetCommMask(m_hComm, dwCommEvents))///设置通信事件
    		{
    			if (GetCommState(m_hComm, &m_dcb))///获取当前DCB参数
    			{
    				m_dcb.EvtChar = 'q';///设置字件字符
    				m_dcb.fRtsControl = RTS_CONTROL_ENABLE;		// set RTS bit high!
    				if (BuildCommDCB(szBaud, &m_dcb))///填写DCB结构
    				{
    					if (SetCommState(m_hComm, &m_dcb))///配置DCB
    						; // normal operation... continue
    					else
    						ProcessErrorMessage("SetCommState()");
    				}
    				else
    					ProcessErrorMessage("BuildCommDCB()");
    			}
    			else
    				ProcessErrorMessage("GetCommState()");
    		}
    		else
    			ProcessErrorMessage("SetCommMask()");
    	}
    	else
    		ProcessErrorMessage("SetCommTimeouts()");
    
    	delete [] szPort;
    	delete [] szBaud;
    
    	// flush the port
    	///终止读写并清空接收和发送
    	PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
    
    	// release critical section
    	///释放临界资源
    	LeaveCriticalSection(&m_csCommunicationSync);
    
    	TRACE("Initialisation for communicationport %d completed.
    Use Startmonitor to communicate.
    ", portnr);
    
    	return TRUE;
    }
    
    //
    //  The CommThread Function.
    ///线程函数
    ///监视线程的大致流程:
    ///检查串口-->进入循环{WaitCommEvent(不阻塞询问)询问事件-->如果有事件来到-->到相应处理(关闭读写)}
    //
    UINT CSerialPort::CommThread(LPVOID pParam)
    {
    	// Cast the void pointer passed to the thread back to
    	// a pointer of CSerialPort class
    	///CSerialPort类的指针
    	CSerialPort *port = (CSerialPort*)pParam;
    	
    	// Set the status variable in the dialog class to
    	// TRUE to indicate the thread is running.
    	///TRUE表示线程正在运行
    	port->m_bThreadAlive = TRUE;	
    		
    	// Misc. variables
    	DWORD BytesTransfered = 0; 
    	DWORD Event = 0;
    	DWORD CommEvent = 0;
    	DWORD dwError = 0;
    	COMSTAT comstat;
    	BOOL  bResult = TRUE;
    		
    	// Clear comm buffers at startup
    	///开始时清除串口缓冲
    	if (port->m_hComm)		// check if the port is opened
    		PurgeComm(port->m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
    
    	// begin forever loop.  This loop will run as long as the thread is alive.
    	///只要线程存在就不断读取数据
    	for (;;) 
    	{ 
    
    		// Make a call to WaitCommEvent().  This call will return immediatly
    		// because our port was created as an async port (FILE_FLAG_OVERLAPPED
    		// and an m_OverlappedStructerlapped structure specified).  This call will cause the 
    		// m_OverlappedStructerlapped element m_OverlappedStruct.hEvent, which is part of the m_hEventArray to 
    		// be placed in a non-signeled state if there are no bytes available to be read,
    		// or to a signeled state if there are bytes available.  If this event handle 
    		// is set to the non-signeled state, it will be set to signeled when a 
    		// character arrives at the port.
    
    		// we do this for each port!
    
    		/*
    		WaitCommEvent函数第3个参数1pOverlapped可以是一个OVERLAPPED结构的变量指针
    		,也可以是NULL,当用NULL时,表示该函数是同步的,否则表示该函数是异步的。
    		调用WaitCommEvent时,如果异步操作不能立即完成,会立即返回FALSE,系统在
    		WaitCommEvent返回前将OVERLAPPED结构成员hEvent设为无信号状态,等到产生通信
    		事件时,系统将其置有信号
    		*/
    
    		bResult = WaitCommEvent(port->m_hComm, &Event, &port->m_ov);///表示该函数是异步的
    		
    		if (!bResult)  
    		{ 
    			// If WaitCommEvent() returns FALSE, process the last error to determin
    			// the reason..
    			///如果WaitCommEvent返回Error为FALSE,则查询错误信息
    			switch (dwError = GetLastError()) 
    			{ 
    			case ERROR_IO_PENDING: 	///正常情况,没有字符可读
    				{ 
    					// This is a normal return value if there are no bytes
    					// to read at the port.
    					// Do nothing and continue
    					break;
    				}
    			case 87:///系统错误
    				{
    					// Under Windows NT, this value is returned for some reason.
    					// I have not investigated why, but it is also a valid reply
    					// Also do nothing and continue.
    					break;
    				}
    			default:///发生其他错误,其中有串口读写中断开串口连接的错误
    				{
    					// All other error codes indicate a serious error has
    					// occured.  Process this error.
    					port->ProcessErrorMessage("WaitCommEvent()");
    					break;
    				}
    			}
    		}
    		else	///WaitCommEvent()能正确返回
    		{
    			// If WaitCommEvent() returns TRUE, check to be sure there are
    			// actually bytes in the buffer to read.  
    			//
    			// If you are reading more than one byte at a time from the buffer 
    			// (which this program does not do) you will have the situation occur 
    			// where the first byte to arrive will cause the WaitForMultipleObjects() 
    			// function to stop waiting.  The WaitForMultipleObjects() function 
    			// resets the event handle in m_OverlappedStruct.hEvent to the non-signelead state
    			// as it returns.  
    			//
    			// If in the time between the reset of this event and the call to 
    			// ReadFile() more bytes arrive, the m_OverlappedStruct.hEvent handle will be set again
    			// to the signeled state. When the call to ReadFile() occurs, it will 
    			// read all of the bytes from the buffer, and the program will
    			// loop back around to WaitCommEvent().
    			// 
    			// At this point you will be in the situation where m_OverlappedStruct.hEvent is set,
    			// but there are no bytes available to read.  If you proceed and call
    			// ReadFile(), it will return immediatly due to the async port setup, but
    			// GetOverlappedResults() will not return until the next character arrives.
    			//
    			// It is not desirable for the GetOverlappedResults() function to be in 
    			// this state.  The thread shutdown event (event 0) and the WriteFile()
    			// event (Event2) will not work if the thread is blocked by GetOverlappedResults().
    			//
    			// The solution to this is to check the buffer with a call to ClearCommError().
    			// This call will reset the event handle, and if there are no bytes to read
    			// we can loop back through WaitCommEvent() again, then proceed.
    			// If there are really bytes to read, do nothing and proceed.
    		
    			bResult = ClearCommError(port->m_hComm, &dwError, &comstat);
    
    			if (comstat.cbInQue == 0)
    				continue;
    		}	// end if bResult
    
    		///主等待函数,会阻塞线程
    		// Main wait function.  This function will normally block the thread
    		// until one of nine events occur that require action.
    		///等待3个事件:关断/读/写,有一个事件发生就返回
    		Event = WaitForMultipleObjects(3, ///3个事件
    			port->m_hEventArray, ///事件数组
    			FALSE, ///有一个事件发生就返回
    			INFINITE);///超时时间
    
    		switch (Event)
    		{
    		case 0:
    			{
    				// Shutdown event.  This is event zero so it will be
    				// the higest priority and be serviced first.
    				///关断事件,关闭串口
    				CloseHandle(port->m_hComm);
    				port->m_hComm=NULL;
    				port->m_bThreadAlive = FALSE;
    				
    				// Kill this thread.  break is not needed, but makes me feel better.
    				AfxEndThread(100);
    
    				break;
    			}
    		case 1:	/// read event将定义的各种消息发送出去
    			{
    				memset(&comstat, 0, sizeof(COMSTAT)); 
    				GetCommMask(port->m_hComm, &CommEvent);
    				if (CommEvent & EV_RXCHAR)//接收到字符,并置于输入缓冲区中
    					// Receive character event from port.
    					ReceiveChar(port, comstat);
    				if (CommEvent & EV_CTS)//CTS信号状态发生变化
    					::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_CTS_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);
    				if (CommEvent & EV_BREAK)//输入中发生中断				
    					::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_BREAK_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);
    				if (CommEvent & EV_ERR)//发生线路状态错误,线路状态错误包括CE_FRAME,CE_OVERRUN和CE_RXPARITY 					
    					::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_ERR_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);				
    				if (CommEvent & EV_RING)//检测到振铃指示					
    					::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_RING_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);				
    				if (CommEvent & EV_RXFLAG)//接收到事件字符,并置于输入缓冲区中					
    					::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_RXFLAG_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);
    				
    				break;
    			}  
    		case 2: /// write event发送数据
    			{
    				// Write character event from port
    				WriteChar(port);
    				break;
    			}
    		default:
    			{
    				AfxMessageBox("接收有问题!");
    				break;
    			}
    
    		} // end switch
    
    	} // close forever loop
    
    	return 0;
    }
    
    //
    // start comm watching
    ///开启监视线程
    //
    BOOL CSerialPort::StartMonitoring()
    {
    	if (!(m_Thread = AfxBeginThread(CommThread, this)))
    		return FALSE;
    	TRACE("Thread started
    ");
    	m_bIsSuspened = false;
    	return TRUE;	
    }
    
    //
    // Restart the comm thread
    ///复位监视线程
    //
    BOOL CSerialPort::RestartMonitoring()
    {
    	TRACE("Thread resumed
    ");
    	m_bIsSuspened = false;
    	m_Thread->ResumeThread();
    	return TRUE;	
    }
    
    
    //
    // Suspend the comm thread
    ///挂起监视线程
    //
    BOOL CSerialPort::StopMonitoring()
    {
    	TRACE("Thread suspended
    ");
    	m_bIsSuspened = true;
    	m_Thread->SuspendThread(); 
    	return TRUE;	
    }
    
    
    //
    // If there is a error, give the right message
    ///如果有错误,给出提示
    //
    void CSerialPort::ProcessErrorMessage(char* ErrorText)
    {
    	char *Temp = new char[200];
    	
    	LPVOID lpMsgBuf;
    
    	FormatMessage( 
    		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
    		NULL,
    		GetLastError(),
    		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
    		(LPTSTR) &lpMsgBuf,
    		0,
    		NULL 
    	);
    
    	sprintf(Temp, "WARNING:  %s Failed with the following error: 
    %s
    Port: %d
    ", (char*)ErrorText, lpMsgBuf, m_nPortNr); 
    	MessageBox(NULL, Temp, "Application Error", MB_ICONSTOP);
    
    	LocalFree(lpMsgBuf);
    	delete [] Temp;
    	return;///
    }
    
    //
    // Write a character.
    ///写数据
    //
    void CSerialPort::WriteChar(CSerialPort* port)
    {
    	BOOL bWrite = TRUE;
    	BOOL bResult = TRUE;
    
    	DWORD BytesSent = 0;
    
    	ResetEvent(port->m_hWriteEvent);///复位写事件句柄
    
    	// Gain ownership of the critical section
    	EnterCriticalSection(&port->m_csCommunicationSync);
    
    	if (bWrite)
    	{
    		// Initailize variables
    		port->m_ov.Offset = 0;
    		port->m_ov.OffsetHigh = 0;
    
    		// Clear buffer
    		PurgeComm(port->m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
    
    		///串口写入
    		bResult = WriteFile(port->m_hComm,							// Handle to COMM Port
    							port->m_szWriteBuffer,					// Pointer to message buffer in calling finction
    //							strlen((char*)port->m_szWriteBuffer),	// Length of message to send
    							port->m_nWriteSize,	// Length of message to send // add by mrlong
    							&BytesSent,								// Where to store the number of bytes sent
    							&port->m_ov);							// Overlapped structure
    
    		// deal with any error codes
    		if (!bResult)  
    		{
    			DWORD dwError = GetLastError();
    			switch (dwError)
    			{
    				case ERROR_IO_PENDING:
    					{
    						// continue to GetOverlappedResults()
    						BytesSent = 0;
    						bWrite = FALSE;
    						break;
    					}
    				default:
    					{
    						// all other error codes
    						port->ProcessErrorMessage("WriteFile()");
    						break;///
    					}
    			}
    		} 
    		else
    		{
    			LeaveCriticalSection(&port->m_csCommunicationSync);
    		}
    	} // end if(bWrite)
    
    	if (!bWrite)
    	{
    		bWrite = TRUE;
    	
    		bResult = GetOverlappedResult(port->m_hComm,	// Handle to COMM port 
    									  &port->m_ov,		// Overlapped structure
    									  &BytesSent,		// Stores number of bytes sent
    									  TRUE); 			// Wait flag
    
    		LeaveCriticalSection(&port->m_csCommunicationSync);
    
    		// deal with the error code 
    		//if (!bResult)  ///注释掉了,为什么?
    		{
    			//port->ProcessErrorMessage("GetOverlappedResults() in WriteFile()");
    		}	
    	} // end if (!bWrite)
    
    	//Verify that the data size send equals what we tried to send
    	if (BytesSent != port->m_nWriteSize)	// Length of message to send)
    	{
    		TRACE("WARNING: WriteFile() error.. Bytes Sent: %d; Message Length: %d
    ", BytesSent, strlen((char*)port->m_szWriteBuffer));
    	}
    //	::SendMessage((port->m_pOwner)->m_hWnd, WM_COMM_TXEMPTY_DETECTED, (WPARAM) RXBuff, (LPARAM) port->m_nPortNr);
    //	::SendMessage((port->m_pOwner)->m_hWnd, WM_COMM_TXEMPTY_DETECTED,0,(LPARAM) port->m_nPortNr);
    }
    
    //
    // Character received. Inform the owner
    ///读数据
    //
    void CSerialPort::ReceiveChar(CSerialPort* port, COMSTAT comstat)
    {
    	BOOL  bRead = TRUE; 
    	BOOL  bResult = TRUE;
    	DWORD dwError = 0;
    	DWORD BytesRead = 0;
    	unsigned char RXBuff;
    
    	for (;;) 
    	{ 
    		//add by liquanhai  防止死锁   2011-11-06
    		if(WaitForSingleObject(port->m_hShutdownEvent,0) == WAIT_OBJECT_0)
                return;
    
    		// Gain ownership of the comm port critical section.
    		// This process guarantees no other part of this program 
    		// is using the port object. 
    		
    		EnterCriticalSection(&port->m_csCommunicationSync);
    
    		// ClearCommError() will update the COMSTAT structure and
    		// clear any other errors.
    		///更新COMSTAT
    		
    		bResult = ClearCommError(port->m_hComm, &dwError, &comstat);
    
    		LeaveCriticalSection(&port->m_csCommunicationSync);
    
    		// start forever loop.  I use this type of loop because I
    		// do not know at runtime how many loops this will have to
    		// run. My solution is to start a forever loop and to
    		// break out of it when I have processed all of the
    		// data available.  Be careful with this approach and
    		// be sure your loop will exit.
    		// My reasons for this are not as clear in this sample 
    		// as it is in my production code, but I have found this 
    		// solutiion to be the most efficient way to do this.
    		
    		///所有字符均被读出,中断循环
    		if (comstat.cbInQue == 0)
    		{
    			// break out when all bytes have been read
    			break;
    		}
    						
    		EnterCriticalSection(&port->m_csCommunicationSync);
    
    		if (bRead)
    		{
    			///串口读出,读出缓冲区中字节
    			bResult = ReadFile(port->m_hComm,		// Handle to COMM port 
    							   &RXBuff,				// RX Buffer Pointer
    							   1,					// Read one byte
    							   &BytesRead,			// Stores number of bytes read
    							   &port->m_ov);		// pointer to the m_ov structure
    			// deal with the error code 
    			///若返回错误,错误处理
    			if (!bResult)  
    			{ 
    				switch (dwError = GetLastError()) 
    				{ 
    					case ERROR_IO_PENDING: 	
    						{ 
    							// asynchronous i/o is still in progress 
    							// Proceed on to GetOverlappedResults();
    							///异步IO仍在进行
    							bRead = FALSE;
    							break;
    						}
    					default:
    						{
    							// Another error has occured.  Process this error.
    							port->ProcessErrorMessage("ReadFile()");
    							break;
    							//return;///防止读写数据时,串口非正常断开导致死循环一直执行。add by itas109 2014-01-09 与上面liquanhai添加防死锁的代码差不多
    						} 
    				}
    			}
    			else///ReadFile返回TRUE
    			{
    				// ReadFile() returned complete. It is not necessary to call GetOverlappedResults()
    				bRead = TRUE;
    			}
    		}  // close if (bRead)
    
    		///异步IO操作仍在进行,需要调用GetOverlappedResult查询
    		if (!bRead)
    		{
    			bRead = TRUE;
    			bResult = GetOverlappedResult(port->m_hComm,	// Handle to COMM port 
    										  &port->m_ov,		// Overlapped structure
    										  &BytesRead,		// Stores number of bytes read
    										  TRUE); 			// Wait flag
    
    			// deal with the error code 
    			if (!bResult)  
    			{
    				port->ProcessErrorMessage("GetOverlappedResults() in ReadFile()");
    			}	
    		}  // close if (!bRead)
    				
    		LeaveCriticalSection(&port->m_csCommunicationSync);
    
    		// notify parent that a byte was received
    		::SendMessage((port->m_pOwner)->m_hWnd, WM_COMM_RXCHAR, (WPARAM) RXBuff, (LPARAM) port->m_nPortNr);
    	} // end forever loop
    
    }
    
    //
    // Write a string to the port
    //
    void CSerialPort::WriteToPort(char* string)
    {		
    	assert(m_hComm != 0);
    	
    	memset(m_szWriteBuffer, 0, sizeof(m_szWriteBuffer));
    	strcpy(m_szWriteBuffer, string);
    	m_nWriteSize=strlen(string);
    
    	// set event for write
    	SetEvent(m_hWriteEvent);
    }
    
    
    void CSerialPort::WriteToPort(char* string,int n)
    {		
    	assert(m_hComm != 0);
    
    	memset(m_szWriteBuffer, 0, sizeof(m_szWriteBuffer));
    //	memset(m_szWriteBuffer, 0, n);
    //	strncpy(m_szWriteBuffer, string, n);
    	memcpy(m_szWriteBuffer, string, n);
    	m_nWriteSize=n;
    
    	// set event for write
    	SetEvent(m_hWriteEvent);
    }
    
    void CSerialPort::WriteToPort(LPCTSTR string,int n)
    {		
    	assert(m_hComm != 0);
    
    	memset(m_szWriteBuffer, 0, sizeof(m_szWriteBuffer));
    	memcpy(m_szWriteBuffer, string, n);
    	m_nWriteSize = n;
    
    	// set event for write
    	SetEvent(m_hWriteEvent);
    }
    
    void CSerialPort::WriteToPort(LPCTSTR string)
    {		
    	assert(m_hComm != 0);
    	
    	memset(m_szWriteBuffer, 0, sizeof(m_szWriteBuffer));
    	strcpy(m_szWriteBuffer, string);
    	m_nWriteSize=strlen(string);
    	
    	// set event for write
    	SetEvent(m_hWriteEvent);
    }
    
    void CSerialPort::WriteToPort(BYTE* Buffer, int n)
    {
    	assert(m_hComm != 0);
    	memset(m_szWriteBuffer, 0, sizeof(m_szWriteBuffer));
    	int i;
    	for(i=0; i<n; i++)
    	{
    		m_szWriteBuffer[i] = Buffer[i];
    	}
    	m_nWriteSize=n;
    	
    	// set event for write
    	SetEvent(m_hWriteEvent);
    }
    
    //
    // Return the device control block
    //
    DCB CSerialPort::GetDCB()
    {
    	return m_dcb;
    }
    
    //
    // Return the communication event masks
    //
    DWORD CSerialPort::GetCommEvents()
    {
    	return m_dwCommEvents;
    }
    
    //
    // Return the output buffer size
    //
    DWORD CSerialPort::GetWriteBufferSize()
    {
    	return m_nWriteBufferSize;
    }
    
    
    void CSerialPort::ClosePort()
    {
        if(m_bIsSuspened)
    	{
    		RestartMonitoring();
    	}
        if (m_bThreadAlive)
        {
            MSG message;
            while (m_bThreadAlive)
            {
    			//add by liquanhai  防止死锁  2011-11-06
                if(::PeekMessage(&message, m_pOwner->m_hWnd, 0, 0, PM_REMOVE))
                {
                    ::TranslateMessage(&message);
                    ::DispatchMessage(&message);
                }
                SetEvent(m_hShutdownEvent);
            }
            TRACE("Thread ended
    ");
    
        }
        if(m_szWriteBuffer != NULL)
        {
            delete [] m_szWriteBuffer;
            m_szWriteBuffer = NULL;
        }
    
        if(m_hComm)
        {
            CloseHandle(m_hComm);
            m_hComm = NULL;
        }
    
    	// Close Handles  
    	if(m_hShutdownEvent!=NULL)
    		ResetEvent(m_hShutdownEvent);
    	if(m_ov.hEvent!=NULL)
    		ResetEvent(m_ov.hEvent);
    	if(m_hWriteEvent!=NULL)
    		ResetEvent(m_hWriteEvent);
    }
    
    void CSerialPort::SendData(LPCTSTR lpszData, const int nLength)
    {
    	assert(m_hComm != 0);
    	memset(m_szWriteBuffer, 0, nLength);
    	strcpy(m_szWriteBuffer, lpszData);
    	m_nWriteSize=nLength;
    	// set event for write
    	SetEvent(m_hWriteEvent);
    }
    
    BOOL CSerialPort::RecvData(LPTSTR lpszData, const int nSize)
    {
        //
        //接收数据
        //
        assert(m_hComm!=0);
        memset(lpszData,0,nSize);
        DWORD mylen  = 0;
        DWORD mylen2 = 0;
        while (mylen < nSize) {
            if(!ReadFile(m_hComm,lpszData,nSize,&mylen2,NULL)) 
                return FALSE;
            mylen += mylen2;   
        }
        
        return TRUE;
    }
    
    COMMTIMEOUTS CSerialPort:: GetCommTimeOuts()
    {
    	return m_CommTimeouts;
    }
    
    BOOL CSerialPort::SetCommTimeOuts(COMMTIMEOUTS *lpTimeOuts)
    {
        SetCommTimeouts(m_hComm, lpTimeOuts);//设置超时 
    	return true;
    }
    /*
    void CSerialPort::ClosePort()
    {
    	do
    	{
    		SetEvent(m_hShutdownEvent);
    	} while (m_bThreadAlive);
    
    	
    	// if the port is still opened: close it 
    	if (m_hComm != NULL)
    	{
    		CloseHandle(m_hComm);
    		m_hComm = NULL;
    	}
    	// Close Handles  
    	if(m_hShutdownEvent!=NULL)
    		CloseHandle( m_hShutdownEvent); 
    	if(m_ov.hEvent!=NULL)
    		CloseHandle( m_ov.hEvent ); 
    	if(m_hWriteEvent!=NULL)
    		CloseHandle( m_hWriteEvent ); 
    
    	TRACE("Thread ended
    ");
    	delete [] m_szWriteBuffer;
    }
    */
    
    //
    ///查询注册表的串口号,将值存于数组中
    ///本代码参考于mingojiang的获取串口逻辑名代码
    //
    void CSerialPort::QueryKey(HKEY hKey) 
    { 
    	#define MAX_KEY_LENGTH 255
    	#define MAX_VALUE_NAME 16383
    	//	TCHAR    achKey[MAX_KEY_LENGTH];   // buffer for subkey name
    	//	DWORD    cbName;                   // size of name string 
    	TCHAR    achClass[MAX_PATH] = TEXT("");  // buffer for class name 
    	DWORD    cchClassName = MAX_PATH;  // size of class string 
    	DWORD    cSubKeys=0;               // number of subkeys 
    	DWORD    cbMaxSubKey;              // longest subkey size 
    	DWORD    cchMaxClass;              // longest class string 
    	DWORD    cValues;              // number of values for key 
    	DWORD    cchMaxValue;          // longest value name 
    	DWORD    cbMaxValueData;       // longest value data 
    	DWORD    cbSecurityDescriptor; // size of security descriptor 
    	FILETIME ftLastWriteTime;      // last write time 
    	
    	DWORD i, retCode; 
    	
    	TCHAR  achValue[MAX_VALUE_NAME]; 
    	DWORD cchValue = MAX_VALUE_NAME; 
    	
    	// Get the class name and the value count. 
    	retCode = RegQueryInfoKey(
    		hKey,                    // key handle 
    		achClass,                // buffer for class name 
    		&cchClassName,           // size of class string 
    		NULL,                    // reserved 
    		&cSubKeys,               // number of subkeys 
    		&cbMaxSubKey,            // longest subkey size 
    		&cchMaxClass,            // longest class string 
    		&cValues,                // number of values for this key 
    		&cchMaxValue,            // longest value name 
    		&cbMaxValueData,         // longest value data 
    		&cbSecurityDescriptor,   // security descriptor 
    		&ftLastWriteTime);       // last write time 
    	
    	for (i=0;i<20;i++)///存放串口号的数组初始化
    	{
    		m_nComArray[i] = -1;
    	}
    	
    	// Enumerate the key values. 
    	if (cValues > 0) {
    		for (i=0, retCode=ERROR_SUCCESS; i<cValues; i++) { 
    			cchValue = MAX_VALUE_NAME;  achValue[0] = ''; 
    			if (ERROR_SUCCESS == RegEnumValue(hKey, i, achValue, &cchValue, NULL, NULL, NULL, NULL))  { 
    				CString szName(achValue);
    				if (-1 != szName.Find(_T("Serial")) || -1 != szName.Find(_T("VCom")) ){
    					BYTE strDSName[10]; memset(strDSName, 0, 10);
    					DWORD nValueType = 0, nBuffLen = 10;
    					if (ERROR_SUCCESS == RegQueryValueEx(hKey, (LPCTSTR)achValue, NULL, &nValueType, strDSName, &nBuffLen)){
    						int nIndex = -1;
    						while(++nIndex < MaxSerialPortNum){
    							if (-1 == m_nComArray[nIndex]) {
    								m_nComArray[nIndex] = atoi((char*)(strDSName + 3));
    								break;
    							}
    						}
    					}
    				}
    			} 
    		}
    	}
    	else{
    		AfxMessageBox(_T("本机没有串口....."));
    	}
    	
    }
    
    void CSerialPort::Hkey2ComboBox(CComboBox& m_PortNO)
    {
    	HKEY hTestKey;
    	bool Flag = FALSE;
    	
    	///仅是XP系统的注册表位置,其他系统根据实际情况做修改
    	if(ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("HARDWARE\DEVICEMAP\SERIALCOMM"), 0, KEY_READ, &hTestKey) ){
    		QueryKey(hTestKey);
    	}
    	RegCloseKey(hTestKey);
    	
    	int i = 0;
    	m_PortNO.ResetContent();///刷新时,清空下拉列表内容
    	while(i < MaxSerialPortNum && -1 != m_nComArray[i]){
    		CString szCom; 
    		szCom.Format(_T("COM%d"), m_nComArray[i]);
    		m_PortNO.InsertString(i, szCom.GetBuffer(5));
    		++i;
    		Flag = TRUE;
    		if (Flag)///把第一个发现的串口设为下拉列表的默认值
    			m_PortNO.SetCurSel(0);
    	}
    	
    }
  • 相关阅读:
    最短路之浇水
    agc031_d A Sequence of Permutations
    loj 3236 [POI2019 R1] Układ scalony
    CodeForces 1237H Balanced Reversals
    CodeForces 1320F Blocks and Sensors
    CodeForces 1340D Nastya and Time Machine
    agc037_f Counting of Subarrays
    nikkei2019_2_qual_e Non-triangular Triplets
    CodeForces 603E Pastoral Oddities
    Vue router / ElementUI,Ant Design Vue 重复点击导航路由报错解决方法
  • 原文地址:https://www.cnblogs.com/zjoch/p/4247835.html
Copyright © 2020-2023  润新知