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

来源:互联网 发布:淘宝卖家开花呗条件 编辑:程序博客网 时间:2024/06/08 13:18

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

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

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;                       //将连接标志置0return 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 : 线程创建以后,会处于挂起状态,直到调用:ResumeThread0 : 创建线程后就开始运行.  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/OWIN32的一项技术,你可以要求操作系统为你传送数据,并且在传送完毕时通知你。这项技术使你的程序在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()函数进行详细的介绍。
 
原创粉丝点击