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