1. 程式人生 > >MFC之路 串列埠通訊篇(之二)

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/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()函式進行詳細的介紹。