MFC之路 串口通信篇(之二)

在前面一个章节的文章中,我们对串口进行了打开和参数的设置,接下来我们需要创建一个新的线程完成对串口的数据监听功能。

创建新的线程,一般分为两个部分,一个是创建一个线程,另一个就是创建线程的响应函数

1、首先,创建新的线程

接前面一节的程序代码:

	//创建工作线程
	if(SetComParameterSucceed)   //如果串口设置成功的话,接着创建新的线程
	{
		m_pThread=AfxBeginThread(ComProce,this,THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED,NULL);  //创建新线程函数,返回线程的指针
		if(m_pThread==NULL)   //如果为NULL,表示创建失败
		{
			CloseHandle(m_hCom);    //关闭前面创建的串口句柄
			AfxMessageBox(_T("线程创建失败!"));   //弹出对话框提醒创建线程失败
			m_bConnected=0;                       //将连接标志置0
			return FALSE;                         //返回
		}
		else
		{
			m_pThread->ResumeThread();       //如果创建新线程成功了,调用线程恢复函数恢复线程
		}
	}
	else                 //如果串口设置没成功,直接返回
	{
		CloseHandle(m_hCom);
		AfxMessageBox(_T("参数设置失败!"));
		m_bConnected=0;
		return FALSE;
	}
	m_bConnected=1;          //串口打开成功并且参数设置完成,而且新线程也创建成功之后,才将连接标志设置为1,否则如果有失败的情况前面已经返回FALSE了

还是对创建线程的过程中个别的函数进行特别说明:

首先是创建线程函数:

m_pThread=AfxBeginThread(ComProce,this,THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED,NULL);  //创建新线程函数,返回线程的指针

其中, CWinThread *m_pThread;   //返回的新创建的监听线程指针。这个地方碰到一个问题,在声明线程响应函数ComProce时,将

UINT ComProce(LPVOID pParam);  //串口监听线程的响应函数,这个定义只能放在这里,放在头文件中出错

放在头文件中是有错误的,必须放在源文件的最前面才行,我也不知道什么原因,希望有大神能够指教。

我们还是说一下创建线程函数的使用方法:

用户界面线程和工作者线程都是由AfxBeginThread创建的。MFC提供了两个重载版的AfxBeginThread,一个用于用户界面线程,另一个用于工作者线程,分别有如下的原型和过程:

用户界面线程的AfxBeginThread的原型如下:
CWinThread* AFXAPI AfxBeginThread(
  CRuntimeClass* pThreadClass,   //  从CWinThread派生的RUNTIME_CLASS类;
  int nPriority,    //指定线程优先级,如果为0,则与创建该线程的线程相同;
  UINT nStackSize,   //指定线程的堆栈大小,如果为0,则与创建该线程的线程相同;
  DWORD dwCreateFlags,  //一个创建标识,如果是CREATE_SUSPENDED,则在悬挂状态创建线程,在线程创建后线程挂起,否则线程在创建后开始线程的执行。
  LPSECURITY_ATTRIBUTES lpSecurityAttrs)   //表示线程的安全属性,NT下有用


工作者线程的AfxBeginThread的原型如下:
CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,  //线程的入口函数,声明一定要如下: UINT MyThreadFunction(LPVOID pParam),不能设置为NULL;
  LPVOID lParam,  //传递入线程的参数,注意它的类型为:LPVOID,所以我们可以传递一个结构体入线程.
  int nPriority = THREAD_PRIORITY_NORMAL,   //线程的优先级,一般设置为 0 .让它和主线程具有共同的优先级.
  UINT nStackSize = 0,  //指定新创建的线程的栈的大小.如果为 0,新创建的线程具有和主线程一样的大小的栈
  DWORD dwCreateFlags = 0,  //指定创建线程以后,线程有怎么样的标志.可以指定两个值:
CREATE_SUSPENDED : 线程创建以后,会处于挂起状态,直到调用:ResumeThread
0 : 创建线程后就开始运行.
  LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL  //指向一个 SECURITY_ATTRIBUTES 的结构体,用它来标志新创建线程的安全性.如果为 NULL,
那么新创建的线程就具有和主线程一样的安全性.
  );//用于创建工作者线程
返回值: 成功时返回一个指向新线程的线程对象的指针,否则NULL。

正是因为dwCreateFlags参数设置为了CREATE_SUSPENDED,即创建新线程后将其挂起,所以在后面接着调用了m_pThread->ResumeThread();       //如果创建新线程成功了,调用线程恢复函数恢复线程。

线程其他相关操作

1、线程的挂起

DWORD SuspendThread(HANDLE hThread)

返回值:成功则返回线程被挂起的次数;失败则返回0XFFFFFFFF。

2、线程的恢复

DWORD ResumeThread(HANDLE hTread)

返回值:成功则返回线程被挂起的次数;失败则返回0XFFFFFFFF。

3、要结束线程的两种方式

(1)、这是最简单的方式,也就是让线程函数执行完成,此时线程正常结束.它会返回一个值,一般0是成功结束,

当然你可以定义自己的认为合适的值来代表线程成功执行.在线程内调用AfxEndThread将会直接结束线程,此时线程的一切资源都会被回收.注意在线程中使用了CString类,则不能用AfxEndThread来进行结束线程,会有内存泄漏,只有当程序结束时,会在输出窗口有提示多少byte泄漏了。因为Cstring的回收有其自己的机制。建议在AfxEndThread之前先进行return。

(2)、如果你想让另一个线程B来结束线程A,那么,你就需要在这两个线程中传递信息。

不管是工作者线程还是界面线程,如果你想在线程结束后得到它的结果,那么你可以调用:

::GetExitCodeThread函数


2、创建新线程的响应函数

//串口线程响应函数
UINT ComProce(LPVOID pParam)
{
//	AfxMessageBox("建立线程开始!");
	OVERLAPPED os;			//重叠操作I/O结构体,一会详细介绍其作用
	DWORD dwMask,dwTrans;   //无符号长整型,标志位
	COMSTAT ComStat;        //包含串口信息的结构体
	DWORD dwErrorFlags;     //错误标志位
	
	CSerialComSoftwareDlg *pDlg=(CSerialComSoftwareDlg *)pParam;   //参数传入为this,即对话框类指针

	memset(&os,0,sizeof(OVERLAPPED));     //清空os结构体
	os.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);    //创建一个事件对象,将其赋值给os结构体
	
	if(os.hEvent==NULL)       //创建事件失败
	{
		AfxMessageBox(_T("不能建立事件对象!"));
		return (UINT)-1;
	}

//	AfxMessageBox("已建立成功!");
	while(pDlg->m_bConnected)   //如果串口通信已经连接
	{
		ClearCommError(pDlg->m_hCom,&dwErrorFlags,&ComStat);  //清除硬件的通讯错误以及获取通讯设备的当前状态
		if(ComStat.cbInQue)    //如果有数据到达
		{
//			AfxMessageBox("receive");
			pDlg->ProcessCOMMNotification(EV_RXCHAR,0);   //串口有数据到达时调用此函数

		}
		dwMask=0;   //没有数据时置0
//		AfxMessageBox("no data in!");
		if(!WaitCommEvent(pDlg->m_hCom,&dwMask,&os))   //为一个特指的通信设备等待一个事件发生,成功返回非0,失败返回0
		{
//			AfxMessageBox("wait event");
			if(GetLastError()==ERROR_IO_PENDING)      //如果错误信息为ERROR_IO_PENDING,表示数据正在传输中
			{
//				AfxMessageBox("begin wait a data in!");
				GetOverlappedResult(pDlg->m_hCom,&os,&dwTrans,TRUE);     //判断一个重叠操作的当前状态
//				AfxMessageBox("now,there is!");
			}
			else             //如果错误信息为其他,说明通信出现问题,结束串口通信线程
			{         
				CloseHandle(os.hEvent);
				return(UINT)-1;
			}
//			AfxMessageBox("wait end");
		}
	}
	CloseHandle(os.hEvent);      //线程结束,关闭事件
//	AfxMessageBox("线程结束!");
	return 0;
}


有几个需要说明的地方:

第一个是,OVERLAPPED结构体,这个结构体中记录了串口操作的一些信息。

typedef struct _OVERLAPPED { 
  DWORD Internal;  //预留给操作系统使用
  DWORD InternalHigh;  //预留给操作系统使用
  DWORD Offset;        //该文件的位置是从文件起始处的字节偏移量。
  DWORD OffsetHigh;    //指定文件传送的字节偏移量的高位字
  HANDLE hEvent;       //在转移完成时处理一个事件设置为有信号状态
  } OVERLAPPED

overlapped I/O是WIN32的一项技术,你可以要求操作系统为你传送数据,并且在传送完毕时通知你。这项技术使你的程序在I/O进行过程中仍然能够继续处理事务。事实上,操作系统内部正是以线程来I/O完成overlapped I/O。你可以获得线程的所有利益,而不需付出什么痛苦的代价。

那么怎么设定对串口的操作是否采用OVERLAPPED的方式呢?使用CreateFile (),将其第6个参数指定为FILE_FLAG_OVERLAPPED,
就是准备使用overlapped的方式构造或打开文件,在我们前面的代码中正是应用了这种方式。

第二个是,结构体COMSTAT,这个结构体记录了串口的信息。

typedef struct _COMSTAT { // cst 

    DWORD fCtsHold : 1;   // Tx waiting for CTS signal

    DWORD fDsrHold : 1;   // Tx waiting for DSR signal

    DWORD fRlsdHold : 1;  // Tx waiting for RLSD signal

    DWORD fXoffHold : 1;  // Tx waiting, XOFF char rec''d

    DWORD fXoffSent : 1;  // Tx waiting, XOFF char sent

    DWORD fEof : 1;       // EOF character sent

    DWORD fTxim : 1;      // character waiting for Tx

    DWORD fReserved : 25; // reserved   保留

    DWORD cbInQue;        // bytes in input buffer该成员变量的值代表输入缓冲区的字节数

    DWORD cbOutQue;       // bytes in output buffer记录着输出缓冲区中字节数

} COMSTAT, *LPCOMSTAT;


第三个是,CreateEvent()函数

os.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);    //创建一个事件对象,将其赋值给os结构体

CreateEvent是一个Windows API函数。它用来创建或打开一个命名的或无名的事件对象。

HANDLE  CreateEvent(
LPSECURITY_ATTRIBUTESlpEventAttributes,// 安全属性,确定返回的句柄是否可被子进程继承。如果是NULL,此句柄不能被继承。
BOOLbManualReset,// 复位方式,指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态                  //复原到无信号状态。如果设置为FALSE,当一个等待线程被释放以后,系统将会自动将事件状态复原为无信号状态。
BOOLbInitialState,// 初始状态,指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态;否则为无信号状态
LPCTSTRlpName // 对象名称,指定事件的对象的名称,是一个以0结束的字符串指针。如果lpName为NULL,将创建一个无名的事件对象
);

我们在此处创建的是一个无名的,不能被继承的,初始状态为无信号的,能够自动复原的事件。

第四个是,ClearCommError()函数
Windows系统利用此函数清除硬件的通讯错误以及获取通讯设备的当前状态,ClearCommError函数声明如下:
BOOL ClearCommError(
HANDLE hFile,   //由CreateFile函数返回指向已打开串行口的句柄
LPDWORD lpErrors, //指向定义了错误类型的32位变量
LPCOMSTAT lpStat  //指向一个返回设备状态的控制块COMSTAT
);

第五个是,WaitCommEvent()函数。
WaitCommEvent(pDlg->m_hCom,&dwMask,&os)
作用:为一个特指的通信设备等待一个事件发生,该函数所监控的事件是与该设备句柄相关联的一系列事件。
BOOL WINAPI WaitCommEvent(
__in HANDLEhFile,  //指向通信设备的一个句柄,该句柄应该是由 CreateFile函数返回的。
__out LPDWORDlpEvtMask,  //一个指向DWORD的指针。如果发生错误,pEvtMask指向0,否则指向以下的某一事件
__in LPOVERLAPPEDlpOverlapped   //指向OVERLAPPED结构体的一个指针。如果hFile是用异步方式打开的(在CreateFile()函数中,第三个参数设置为FILE_F                                //LAG_OVERLAPPED)lpOverlapped不能指向一个空OVERLAPPED结构体,而是与Readfile()和WriteFile()中的OVE                                //RLAPPED参数为同一个参数。如果hFile是用异步方式打开的,而lpOverlapped指向一个空的OVERLAPPED结构体,那么函数/                                //会错误地报告,等待的操作已经完成(而此时等待的操作可能还没有完成)。

  
  
//如果 hFile是用异步方式打开的,而 lpOverlapped指向一个非空的 OVERLAPPED结构体,那么函数WaitCommEvent被默认为异 //步操作,马上返回。这时, OVERLAPPED结构体必须包含一个由 CreateEvent()函数返回的手动重置 事件对象的句柄hEven。
);
返回值:
如果函数成功,返回非零值,否则返回0。要得到错误信息,可以调用 GetLastError函数。

第六个是,GetOverlappedResult()函数
GetOverlappedResult(pDlg->m_hCom,&os,&dwTrans,TRUE);     //判断一个重叠操作的当前状态
GetOverlappedResult函数:
BOOL GetOverlappedResult(
HANDLE hFile,        // 串口的句柄
LPOVERLAPPED lpOverlapped,   // 指向重叠操作开始时指定的OVERLAPPED结构
LPDWORD lpNumberOfBytesTransferred,  // 指向一个32位变量,该变量的值返回实际读写操作传输的字节数。
BOOL bWait      // 该参数用于指定函数是否一直等到重叠操作结束。
                // 如果该参数为TRUE,函数直到操作结束才返回。
                // 如果该参数为FALSE,函数直接返回,这时如果操作没有完成,
                // 通过调用GetLastError()函数会返回ERROR_IO_INCOMPLETE。

);
至此,关于串口监听线程的响应函数也完成了。当
if(ComStat.cbInQue)    //如果有读缓冲区中有数据
                {
			pDlg->ProcessCOMMNotification(EV_RXCHAR,0);   //串口有数据到达时调用此函数

		}
程序将会进入到主程序的ProcessCOMMNotification(EV_RXCHAR,0);函数进行数据的进一步处理。
下一节中我们将会对ProcessCOMMNotification()函数进行详细的介绍。



  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值