WinXP與WinCE串口的運行機制之比較
//======================================================================== //TITLE: // WinXP與WinCE串口的運行機制之比較 //AUTHOR: // norains //DATE: // Saturday 11-November-2006 //Passed Environment: // PC:WinXP+VC6.0 // CE:WinCE4.2+EVC4.0 //======================================================================== 查看微軟相關(guān)的串口通信文檔,可以發(fā)現(xiàn)在桌面操作系統(tǒng)中,串口通信分為兩種模式:同步和異步.而WinCE只有一種,但文檔中卻沒標明歸屬哪種模式.實際上,WinCE的串口通信模式更像介于同步和異步之間. 在此先簡要地介紹何為同步和異步.所謂的同步,指得是對同一個設(shè)備或文件(在文中只的是串口COM1)的讀或?qū)懖僮鞅仨氁却弦粋€操作完成才能進行.比如說,調(diào)用ReadFile()函數(shù)讀取串口,但由于上一個WriteFile()操作沒完成,ReadFile()的操作就被阻塞,直到WriteFile()完成后才能運行.而異步,則無論上一個操作是否完成,都會執(zhí)行目前調(diào)用的操作.還是拿前面舉的例子,在異步模式下,即使WriteFile()沒有執(zhí)行完成,ReadFile()也會立刻執(zhí)行. 1.CreateFile()參數(shù)的差異 首先說明一下WinCE和WinXP打開串口時參數(shù)的差異.以打開串口COM1為例子,WinCE下的名字為"COM1:",而WinXP為"COM1",兩者的唯一區(qū)別僅僅在于WinCE下多個分號. 例如: HANDLE hd = CreateFile(TEXT("COM1:"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL); //WinCE HANDLE hd = CreateFile(TEXT("COM1"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL); //WinXP 在這稍微多說一下,在默認的環(huán)境下,TEXT宏在WinCE下會編譯為雙字節(jié),而WinXP為單字節(jié).換句話說,TEXT("COM1")在WinCE下相當于L"COM1",而WinXP則為"COM1". 2.單線程比較 還是用代碼做例子來說明比較形象.這是一段單線程的代碼,先對串口進行寫操作,然后再讀.對于WinXP來說,這是同步模式.(與主題無關(guān)的代碼省略) int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { ... HANDLE hCom = CreateFile(TEXT("COM1:"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL); //WinCE //HANDLE Com = CreateFile(TEXT("COM1"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL); //WinXP ... DWORD dwBytes; if(WriteFile(hCom,TEXT("COM1:"),5,&dwBytes,NULL) == FALSE) //WinCE //if(WriteFile(hCom,TEXT("COM1"),5,&dwBytes,NULL) == FALSE) //WinXP { return 0x05; } ... DWORD dwRead; char szBuf[MAX_READ_BUFFER]; if(ReadFile(hCom,szBuf,MAX_READ_BUFFER,&dwRead,NULL) == FALSE) { return 0x10; } ... } 經(jīng)過實驗,可以發(fā)現(xiàn)這段代碼在WinCE和WinXP下都能正常工作,并且其表現(xiàn)也相同,都是在WriteFile()函數(shù)返回后才執(zhí)行ReadFile(). 由于異步模式在單線程中也能正常運作,唯一的不同只是在執(zhí)行WriteFile()時可能也會執(zhí)行ReadFile()(依WriteFile()函數(shù)執(zhí)行的時間而定),所在此不表. 3.多線程比較 單線程兩者表現(xiàn)相同,那多線程呢?下面這段代碼采用多線程,先是創(chuàng)建一個讀的線程,用來監(jiān)控串口是否有新數(shù)據(jù)到達,然后在主線程中對串口寫出數(shù)據(jù). 這里假設(shè)是這么一個情況,有兩臺設(shè)備,分別為A和B,下面的代碼運行于設(shè)備A,設(shè)備B僅僅只是應答而已.換句話說,只有A向B發(fā)送數(shù)據(jù),B才會返回應答信號. //主線程 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { ... CreateThread(NULL,0,ReadThread,0,0,&dwThrdID); //創(chuàng)建一個讀的線程. ... HANDLE hCom = CreateFile(TEXT("COM1:"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL); //WinCE //HANDLE Com = CreateFile(TEXT("COM1"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL); //WinXP ... DWORD dwBytes; if(WriteFile(hCom,"AT\r\n",4,&dwBytes,NULL) == FALSE) //WinCE //if(WriteFile(hCom,"AT\r\n",5,&dwBytes,NULL) == FALSE) //WinXP { return 0x05; } ... } //讀線程 DWORD WINAPI ReadThread() { ... SetCommMask(hCom),EV_RXCHAR); DWORD dwCommStatus = 0; if(WaitCommEvent(hCom),&dwCommStatus,NULL) == FALSE) { //Clear the error flag DWORD dwErrors; COMSTAT comStat; memset(&comStat,0,sizeof(comStat)); ClearCommError(hCom,&dwErrors,&comStat); return 0x15; } ... char szBuf[MAX_READ_BUFFER]={0}; DWORD dwRead; if(ReadFile(hCom),szBuf,MAX_READ_BUFFER,&dwRead,NULL) == FALSE || dwRead == 0) { return 0x20; } ... } 這段代碼在WinCE下運行完全正常,讀線程在監(jiān)聽收到的數(shù)據(jù)的同時,主線程順利地往外發(fā)數(shù)據(jù). 然而同樣的代碼,在WinXP下則根本無法完成工作.運行此代碼,我們將發(fā)現(xiàn)CPU的占用率高達99%.通過單步調(diào)試,發(fā)現(xiàn)兩個線程分別卡在WaitCommEvent()和WriteFile()函數(shù)中.因為根據(jù)同步模式的定義,當前對設(shè)備的操作必須要等待上一個操作完畢方可執(zhí)行.在以上代碼中,因為設(shè)備B沒接到設(shè)備A的命令而不會向設(shè)備A發(fā)送應答,故WaitCommEvent()函數(shù)因為沒有檢測到接受數(shù)據(jù)而一直在占用串口;而WaitCommEvent()一直占據(jù)串口使得WriteFile()沒有得到串口資源而處于阻塞狀態(tài),這就造成了死鎖. 而這種情況沒有在WinCE上出現(xiàn),只要WaitCommEvent()和WriteFile()不在同一個線程,就可以正常工作.這應該和系統(tǒng)的調(diào)度方式有關(guān). 如果要在PC上同時進行WaitCommEvent()和WriteFile()操作,需要把串口的模式改寫為異步模式. 更改后的代碼如下: //主線程 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { ... CreateThread(NULL,0,ReadThread,0,0,&dwThrdID); //創(chuàng)建一個讀的線程. ... HANDLE Com = CreateFile(TEXT("COM1"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,FILE_FLAG_OVERLAPPED); ... OVERLAPPED olWrite; memset(&olWrite,0,sizeof(m_olWrite)); olWrite.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL); DWORD dwBytes; if(WriteFile(hCom,"AT\r\n",4,&dwBytes,&olWrite) == FALSE) { if(GetLastError() != ERROR_IO_PENDING) { return 0x20; } } if(GetOverlappedResult(hCom,&olWrite,&dwBytes,TRUE) == FALSE) { return 0x25; } ... } //讀線程 DWORD WINAPI ReadThread() { ... memset(&olWaite,0,sizeof(olWaite)); olWaite.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL); SetCommMask(hCom),EV_RXCHAR); DWORD dwCommStatus = 0; WaitCommEvent(hCom,&dwCommStatus,olWaite); DWORD dwByte; //norains:It is only suitable for the GetOverlappedResult(),not undefined here. if(GetOverlappedResult(hCom,olWaite,&dwByte,TRUE) == FALSE) { if(GetLastError() != ERROR_IO_PENDING) { return 0x30; } //Clear the error flag DWORD dwErrors; COMSTAT comStat; memset(&comStat,0,sizeof(comStat)); ClearCommError(hCom,&dwErrors,&comStat); return 0x35; } ... memset(&olRead,0,sizeof(olRead)); olRead.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL); char szBuf[MAX_READ_BUFFER]={0}; DWORD dwRead; if(ReadFile(hCom,szBuf,MAX_READ_BUFFER,&dwRead,olRead) ==FALSE) { if(GetLastError() != ERROR_IO_PENDING) { return 0x40; } if(GetOverlappedResult(hCom,olRead,&dwRead,TRUE) == FALSE) { return 0x45; } if(dwRead == 0) { return 0x50; } } ... } 測試經(jīng)過更改后的代碼,可以發(fā)現(xiàn)在WinXP下終于可以同時調(diào)用WaitCommEvent()和WriteFile()而不造成死鎖. 在這里可以發(fā)現(xiàn)WinCE和WinXP的串口調(diào)度的差異性:單線程中,WinCE的串口工作方式和WinXP串口的同步工作模式相符;而多線程中,WinCE串口工作方式卻又和WinXP的異步方式吻合.雖然無法確切比較WinCE的單一串口模式是否比WinXP的雙模式更為優(yōu)越,但可以確認的是,WinCE的這種串口調(diào)用方式給程序員帶來了極大的便利. 4.WinXP異步模式兩種判斷操作是否成功的方法 因為在WinXP的異步模式中,WriteFile(),ReadFile()和WaitCommEvent()大部分情況下都是未操作完畢就返回,所以不能簡單地判斷返回值是否為TRUE或FALSE來判斷. 以ReadFile()函數(shù)做例子. 一種是上文所用的方法: if(ReadFile(hCom,szBuf,MAX_READ_BUFFER,&dwRead,olRead) ==FALSE) { if(GetLastError() != ERROR_IO_PENDING) { return 0x40; } if(GetOverlappedResult(hCom,olRead,&dwRead,TRUE) == FALSE) { return 0x45; } if(dwRead == 0) { return 0x50; } } 如果ReadFile()返回為TRUE,則表明讀文件已經(jīng)完成.但這種情況幾乎不會出現(xiàn),因為對外設(shè)的讀寫相對于內(nèi)存的讀寫來說非常慢,所以一般在ReadFile()函數(shù)還沒執(zhí)行完畢,程序已經(jīng)執(zhí)行到下一個語句. 當ReadFile()返回為FALSE時,需要采用GetLastError()函數(shù)判斷讀操作是否在后臺進行.如果在后臺進行,則調(diào)用GetOverlappedResult()函數(shù)獲取ReadFile()函數(shù)的結(jié)果.在這里要注意的是,GetOverlappedResult()函數(shù)的最后一個參數(shù)必須設(shè)置為TRUE,表明要等ReadFile()函數(shù)在后臺運行完畢才返回.如果最后一個參數(shù)設(shè)置為FALSE,則即使ReadFile()還在后臺執(zhí)行,GetOverlappedResult()函數(shù)也會立刻返回,從而造成判斷錯誤. 另一種是調(diào)用WaitForSingleObject函數(shù)達到此目的: if(ReadFile(hCom,szBuf,MAX_READ_BUFFER,&dwRead,olRead) ==FALSE) { if(GetLastError() != ERROR_IO_PENDING) { return 0x40; } if(WaitForSingleObject(olRead.hEvent,INFINITE) != WAIT_OBJECT_0) { return 0x55; } if(GetOverlappedResult(hCom,olRead,&dwRead,FALSE) == FALSE) { return 0x45; } if(dwRead == 0) { return 0x50; } } 因為ReadFile()在后臺執(zhí)行完畢以后,會發(fā)送一個event,所以在這里可以調(diào)用WaitForSingleObject()等待ReadFile()執(zhí)行完畢,然后再調(diào)用GetOverlappedResult()獲取ReadFile()的最終結(jié)果.在這里需要注意的是,GetOverlappedResult()的最后一個參數(shù)一定要設(shè)置為FALSE,因為WaitForSingleObject()已經(jīng)捕獲了ReadFile()發(fā)出的event,再也沒有event傳遞到GetOverlappedResult()函數(shù).如果此時GetOverlappedResult()最后一個參數(shù)設(shè)置為TRUE,則線程會一直停留在GetOverlappedResult()函數(shù)而不往下執(zhí)行. --------------------------------------------------------------------------------
eVC中串口編程思路和VC大致相同,但是有幾點要注意:
1) Windows CE是Unicode編碼,,讀取字符時候,要注意字節(jié)數(shù)的確定,。 2) eVC不支持重疊I/O,所有的函數(shù)中與OVERLAPPED結(jié)構(gòu)有關(guān)的參數(shù)都必須置為 NULL,。 3) eVC不支持BuildCommDCB(),,GetOverlappedResult()。 4) eVC中串口的寫法和一般VC中的寫法不同,,如串口1,,要寫為“COM1:”而不能寫為“COM1”。 本文來自CSDN博客,,轉(zhuǎn)載請標明出處:http://blog.csdn.net/ceFighter/archive/2010/04/27/5532878.aspx |
|