在前面一个章节的文章中,我们对串口进行了打开和参数的设置,接下来我们需要创建一个新的线程完成对串口的数据监听功能。
创建新的线程,一般分为两个部分,一个是创建一个线程,另一个就是创建线程的响应函数
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,将创建一个无名的事件对象
);
我们在此处创建的是一个无名的,不能被继承的,初始状态为无信号的,能够自动复原的事件。
BOOL ClearCommError(
HANDLE hFile, //由CreateFile函数返回指向已打开串行口的句柄
LPDWORD lpErrors, //指向定义了错误类型的32位变量
LPCOMSTAT lpStat //指向一个返回设备状态的控制块COMSTAT
);
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。
);
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()函数进行详细的介绍。