WinXP與WinCE串口的運(yùn)行機(jī)制之比較
//========================================================================
//TITLE:
// WinXP與WinCE串口的運(yùn)行機(jī)制之比較
//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只有一種,但文檔中卻沒標(biāo)明歸屬哪種模式.實(shí)際上,WinCE的串口通信模式更像介于同步和異步之間.
在此先簡(jiǎn)要地介紹何為同步和異步.所謂的同步,指得是對(duì)同一個(gè)設(shè)備或文件(在文中只的是串口COM1)的讀或?qū)懖僮鞅仨氁却弦粋€(gè)操作完成才能進(jìn)行.比如說(shuō),調(diào)用ReadFile()函數(shù)讀取串口,但由于上一個(gè)WriteFile()操作沒完成,ReadFile()的操作就被阻塞,直到WriteFile()完成后才能運(yùn)行.而異步,則無(wú)論上一個(gè)操作是否完成,都會(huì)執(zhí)行目前調(diào)用的操作.還是拿前面舉的例子,在異步模式下,即使WriteFile()沒有執(zhí)行完成,ReadFile()也會(huì)立刻執(zhí)行.
1.CreateFile()參數(shù)的差異
首先說(shuō)明一下WinCE和WinXP打開串口時(shí)參數(shù)的差異.以打開串口COM1為例子,WinCE下的名字為"COM1:",而WinXP為"COM1",兩者的唯一區(qū)別僅僅在于WinCE下多個(gè)分號(hào).
例如:
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
在這稍微多說(shuō)一下,在默認(rèn)的環(huán)境下,TEXT宏在WinCE下會(huì)編譯為雙字節(jié),而WinXP為單字節(jié).換句話說(shuō),TEXT("COM1")在WinCE下相當(dāng)于L"COM1",而WinXP則為"COM1".
2.單線程比較
還是用代碼做例子來(lái)說(shuō)明比較形象.這是一段單線程的代碼,先對(duì)串口進(jìn)行寫操作,然后再讀.對(duì)于WinXP來(lái)說(shuō),這是同步模式.(與主題無(wú)關(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)過實(shí)驗(yàn),可以發(fā)現(xiàn)這段代碼在WinCE和WinXP下都能正常工作,并且其表現(xiàn)也相同,都是在WriteFile()函數(shù)返回后才執(zhí)行ReadFile().
由于異步模式在單線程中也能正常運(yùn)作,唯一的不同只是在執(zhí)行WriteFile()時(shí)可能也會(huì)執(zhí)行ReadFile()(依WriteFile()函數(shù)執(zhí)行的時(shí)間而定),所在此不表.
3.多線程比較
單線程兩者表現(xiàn)相同,那多線程呢?下面這段代碼采用多線程,先是創(chuàng)建一個(gè)讀的線程,用來(lái)監(jiān)控串口是否有新數(shù)據(jù)到達(dá),然后在主線程中對(duì)串口寫出數(shù)據(jù).
這里假設(shè)是這么一個(gè)情況,有兩臺(tái)設(shè)備,分別為A和B,下面的代碼運(yùn)行于設(shè)備A,設(shè)備B僅僅只是應(yīng)答而已.換句話說(shuō),只有A向B發(fā)送數(shù)據(jù),B才會(huì)返回應(yīng)答信號(hào).
//主線程
int WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
...
CreateThread(NULL,0,ReadThread,0,0,&dwThrdID); //創(chuàng)建一個(gè)讀的線程.
...
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下運(yùn)行完全正常,讀線程在監(jiān)聽收到的數(shù)據(jù)的同時(shí),主線程順利地往外發(fā)數(shù)據(jù).
然而同樣的代碼,在WinXP下則根本無(wú)法完成工作.運(yùn)行此代碼,我們將發(fā)現(xiàn)CPU的占用率高達(dá)99%.通過單步調(diào)試,發(fā)現(xiàn)兩個(gè)線程分別卡在WaitCommEvent()和WriteFile()函數(shù)中.因?yàn)楦鶕?jù)同步模式的定義,當(dāng)前對(duì)設(shè)備的操作必須要等待上一個(gè)操作完畢方可執(zhí)行.在以上代碼中,因?yàn)樵O(shè)備B沒接到設(shè)備A的命令而不會(huì)向設(shè)備A發(fā)送應(yīng)答,故WaitCommEvent()函數(shù)因?yàn)闆]有檢測(cè)到接受數(shù)據(jù)而一直在占用串口;而WaitCommEvent()一直占據(jù)串口使得WriteFile()沒有得到串口資源而處于阻塞狀態(tài),這就造成了死鎖.
而這種情況沒有在WinCE上出現(xiàn),只要WaitCommEvent()和WriteFile()不在同一個(gè)線程,就可以正常工作.這應(yīng)該和系統(tǒng)的調(diào)度方式有關(guān).
如果要在PC上同時(shí)進(jìn)行WaitCommEvent()和WriteFile()操作,需要把串口的模式改寫為異步模式.
更改后的代碼如下:
//主線程
int WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
...
CreateThread(NULL,0,ReadThread,0,0,&dwThrdID); //創(chuàng)建一個(gè)讀的線程.
...
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;
}
}
...
}
測(cè)試經(jīng)過更改后的代碼,可以發(fā)現(xiàn)在WinXP下終于可以同時(shí)調(diào)用WaitCommEvent()和WriteFile()而不造成死鎖.
在這里可以發(fā)現(xiàn)WinCE和WinXP的串口調(diào)度的差異性:單線程中,WinCE的串口工作方式和WinXP串口的同步工作模式相符;而多線程中,WinCE串口工作方式卻又和WinXP的異步方式吻合.雖然無(wú)法確切比較WinCE的單一串口模式是否比WinXP的雙模式更為優(yōu)越,但可以確認(rèn)的是,WinCE的這種串口調(diào)用方式給程序員帶來(lái)了極大的便利.
4.WinXP異步模式兩種判斷操作是否成功的方法
因?yàn)樵赪inXP的異步模式中,WriteFile(),ReadFile()和WaitCommEvent()大部分情況下都是未操作完畢就返回,所以不能簡(jiǎn)單地判斷返回值是否為TRUE或FALSE來(lái)判斷.
以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)完成.但這種情況幾乎不會(huì)出現(xiàn),因?yàn)閷?duì)外設(shè)的讀寫相對(duì)于內(nèi)存的讀寫來(lái)說(shuō)非常慢,所以一般在ReadFile()函數(shù)還沒執(zhí)行完畢,程序已經(jīng)執(zhí)行到下一個(gè)語(yǔ)句.
當(dāng)ReadFile()返回為FALSE時(shí),需要采用GetLastError()函數(shù)判斷讀操作是否在后臺(tái)進(jìn)行.如果在后臺(tái)進(jìn)行,則調(diào)用GetOverlappedResult()函數(shù)獲取ReadFile()函數(shù)的結(jié)果.在這里要注意的是,GetOverlappedResult()函數(shù)的最后一個(gè)參數(shù)必須設(shè)置為TRUE,表明要等ReadFile()函數(shù)在后臺(tái)運(yùn)行完畢才返回.如果最后一個(gè)參數(shù)設(shè)置為FALSE,則即使ReadFile()還在后臺(tái)執(zhí)行,GetOverlappedResult()函數(shù)也會(huì)立刻返回,從而造成判斷錯(cuò)誤.
另一種是調(diào)用WaitForSingleObject函數(shù)達(dá)到此目的:
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;
}
}
因?yàn)镽eadFile()在后臺(tái)執(zhí)行完畢以后,會(huì)發(fā)送一個(gè)event,所以在這里可以調(diào)用WaitForSingleObject()等待ReadFile()執(zhí)行完畢,然后再調(diào)用GetOverlappedResult()獲取ReadFile()的最終結(jié)果.在這里需要注意的是,GetOverlappedResult()的最后一個(gè)參數(shù)一定要設(shè)置為FALSE,因?yàn)閃aitForSingleObject()已經(jīng)捕獲了ReadFile()發(fā)出的event,再也沒有event傳遞到GetOverlappedResult()函數(shù).如果此時(shí)GetOverlappedResult()最后一個(gè)參數(shù)設(shè)置為TRUE,則線程會(huì)一直停留在GetOverlappedResult()函數(shù)而不往下執(zhí)行.
--------------------------------------------------------------------------------
eVC中串口編程思路和VC大致相同,但是有幾點(diǎn)要注意:
1) Windows CE是Unicode編碼,讀取字符時(shí)候,要注意字節(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”。