WinXP與WinCE串口的運行機制之比較

 
/========================================================================
//TITLE:
// WinXP與WinCE串口的運行機制之比較
//AUTHOR:
// norains
//DATE:
// Saturday 11-November-2006
//PassedEnvironment:
// PC:WinXP VC6.0
// CE:WinCE4.2 EVC4.0
//========================================================================
查看微軟相關的串口通信文檔,可以發現在桌面操作系統中,串口通信分爲兩種模式:同步和異步.而WinCE只有一種,但文檔中卻沒標明歸屬哪種模式.實際上,WinCE的串口通信模式更像介於同步和異步之間.
在此先簡要地介紹何爲同步和異步.所謂的同步,指得是對同一個設備或文件(在文中只的是串口COM1)的讀或寫操作必須要等待上一個操作完成才能進行.比如說,調用ReadFile()函數讀取串口,但由於上一個WriteFile()操作沒完成,ReadFile()的操作就被阻塞,直到WriteFile()完成後才能運行.而異步,則無論上一個操作是否完成,都會執行目前調用的操作.還是拿前面舉的例子,在異步模式下,即使WriteFile()沒有執行完成,ReadFile()也會立刻執行.
1.CreateFile()參數的差異
首先說明一下WinCE和WinXP打開串口時參數的差異.以打開串口COM1爲例子,WinCE下的名字爲"COM1:",而WinXP爲"COM1",兩者的唯一區別僅僅在於WinCE下多個分號. 
例如:
HANDLEhd=CreateFile(TEXT("COM1:"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);//WinCE
HANDLEhd=CreateFile(TEXT("COM1"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);//WinXP
在這稍微多說一下,在默認的環境下,TEXT宏在WinCE下會編譯爲雙字節,而WinXP爲單字節.換句話說,TEXT("COM1")在WinCE下相當於L"COM1",而WinXP則爲"COM1".
2.單線程比較
還是用代碼做例子來說明比較形象.這是一段單線程的代碼,先對串口進行寫操作,然後再讀.對於WinXP來說,這是同步模式.(與主題無關的代碼省略) 
intWINAPIWinMain( HINSTANCEhInstance,
HINSTANCEhPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
... 
HANDLEhCom=CreateFile(TEXT("COM1:"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);//WinCE
//HANDLECom=CreateFile(TEXT("COM1"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);//WinXP
...
DWORDdwBytes; 軟件開發網 
if(WriteFile(hCom,TEXT("COM1:"),5,&dwBytes,NULL)==FALSE)//WinCE
//if(WriteFile(hCom,TEXT("COM1"),5,&dwBytes,NULL)==FALSE)//WinXP
{
return0x05;
}
...
DWORDdwRead;
charszBuf[MAX_READ_BUFFER];
if(ReadFile(hCom,szBuf,MAX_READ_BUFFER,&dwRead,NULL)==FALSE)
{
return0x10;
}
...
}
經過實驗,可以發現這段代碼在WinCE和WinXP下都能正常工作,並且其表現也相同,都是在WriteFile()函數返回後才執行ReadFile().
由於異步模式在單線程中也能正常運作,唯一的不同只是在執行WriteFile()時可能也會執行ReadFile()(依WriteFile()函數執行的時間而定),所在此不表.
3.多線程比較
單線程兩者表現相同,那多線程呢?下面這段代碼採用多線程,先是創建一個讀的線程,用來監控串口是否有新數據到達,然後在主線程中對串口寫出數據.
這裏假設是這麼一個情況,有兩臺設備,分別爲A和B,下面的代碼運行於設備A,設備B僅僅只是應答而已.換句話說,只有A向B發送數據,B纔會返回應答信號.
//主線程
intWINAPIWinMain( HINSTANCEhInstance,
HINSTANCEhPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
...
CreateThread(NULL,0,ReadThread,0,0,&dwThrdID);//創建一個讀的線程.
... 
HANDLEhCom=CreateFile(TEXT("COM1:"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);//WinCE
//HANDLECom=CreateFile(TEXT("COM1"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);//WinXP
...
DWORDdwBytes;
if(WriteFile(hCom,"AT\r\n",4,&dwBytes,NULL)==FALSE)//WinCE
//if(WriteFile(hCom,"AT\r\n",5,&dwBytes,NULL)==FALSE)//WinXP
{
return0x05;
}
...
}
//讀線程
DWORDWINAPIReadThread()
{
... 
SetCommMask(hCom),EV_RXCHAR);
DWORDdwCommStatus=0;
if(WaitCommEvent(hCom),&dwCommStatus,NULL)==FALSE)
{
//Cleartheerrorflag
DWORDdwErrors;
COMSTATcomStat;
memset(&comStat,0,sizeof(comStat));
ClearCommError(hCom,&dwErrors,&comStat);
return0x15;
}
...
charszBuf[MAX_READ_BUFFER]={0};
DWORDdwRead;
if(ReadFile(hCom),szBuf,MAX_READ_BUFFER,&dwRead,NULL)==FALSE||dwRead==0)
{
return0x20;
}
...
}
這段代碼在WinCE下運行完全正常,讀線程在監聽收到的數據的同時,主線程順利地往外發數據.
然而同樣的代碼,在WinXP下則根本無法完成工作.運行此代碼,我們將發現CPU的佔用率高達99%.通過單步調試,發現兩個線程分別卡在WaitCommEvent()和WriteFile()函數中.因爲根據同步模式的定義,當前對設備的操作必須要等待上一個操作完畢方可執行.在以上代碼中,因爲設備B沒接到設備A的命令而不會向設備A發送應答,故WaitCommEvent()函數因爲沒有檢測到接受數據而一直在佔用串口;而WaitCommEvent()一直佔據串口使得WriteFile()沒有得到串口資源而處於阻塞狀態,這就造成了死鎖. 
而這種情況沒有在WinCE上出現,只要WaitCommEvent()和WriteFile()不在同一個線程,就可以正常工作.這應該和系統的調度方式有關.
如果要在PC上同時進行WaitCommEvent()和WriteFile()操作,需要把串口的模式改寫爲異步模式.
更改後的代碼如下:
//主線程
intWINAPIWinMain( HINSTANCEhInstance,
HINSTANCEhPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
...
CreateThread(NULL,0,ReadThread,0,0,&dwThrdID);//創建一個讀的線程.
... 
HANDLECom=CreateFile(TEXT("COM1"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,FILE_FLAG_OVERLAPPED);
...
OVERLAPPEDolWrite;
memset(&olWrite,0,sizeof(m_olWrite));
olWrite.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
DWORDdwBytes; 
if(WriteFile(hCom,"AT\r\n",4,&dwBytes,&olWrite)==FALSE)
{
if(GetLastError()!=ERROR_IO_PENDING)
{
return0x20;
}
}
if(GetOverlappedResult(hCom,&olWrite,&dwBytes,TRUE)==FALSE)
{
return0x25;
}
...
}
//讀線程
DWORDWINAPIReadThread()
{
... 
memset(&olWaite,0,sizeof(olWaite)); 
olWaite.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL); 
SetCommMask(hCom),EV_RXCHAR);
DWORDdwCommStatus=0;
WaitCommEvent(hCom,&dwCommStatus,olWaite);
DWORDdwByte;//norains:ItisonlysuitablefortheGetOverlappedResult(),notundefinedhere.
if(GetOverlappedResult(hCom,olWaite,&dwByte,TRUE)==FALSE)
{
if(GetLastError()!=ERROR_IO_PENDING)
{
return0x30;
}
//Cleartheerrorflag
DWORDdwErrors;
COMSTATcomStat;
memset(&comStat,0,sizeof(comStat));
ClearCommError(hCom,&dwErrors,&comStat);
return0x35; 
}
...
memset(&olRead,0,sizeof(olRead));
olRead.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
charszBuf[MAX_READ_BUFFER]={0};
DWORDdwRead;
if(ReadFile(hCom,szBuf,MAX_READ_BUFFER,&dwRead,olRead)==FALSE)
{
if(GetLastError()!=ERROR_IO_PENDING)
{
return0x40;
}
if(GetOverlappedResult(hCom,olRead,&dwRead,TRUE)==FALSE)
{
return0x45;
}
if(dwRead==0)
{
return0x50;
}
}
...
}
測試經過更改後的代碼,可以發現在WinXP下終於可以同時調用WaitCommEvent()和WriteFile()而不造成死鎖.
在這裏可以發現WinCE和WinXP的串口調度的差異性:單線程中,WinCE的串口工作方式和WinXP串口的同步工作模式相符;而多線程中,WinCE串口工作方式卻又和WinXP的異步方式吻合.雖然無法確切比較WinCE的單一串口模式是否比WinXP的雙模式更爲優越,但可以確認的是,WinCE的這種串口調用方式給程序員帶來了極大的便利.
4.WinXP異步模式兩種判斷操作是否成功的方法 
因爲在WinXP的異步模式中,WriteFile(),ReadFile()和WaitCommEvent()大部分情況下都是未操作完畢就返回,所以不能簡單地判斷返回值是否爲TRUE或FALSE來判斷.
以ReadFile()函數做例子.
一種是上文所用的方法:
if(ReadFile(hCom,szBuf,MAX_READ_BUFFER,&dwRead,olRead)==FALSE)
{
if(GetLastError()!=ERROR_IO_PENDING)
{
return0x40;
}
if(GetOverlappedResult(hCom,olRead,&dwRead,TRUE)==FALSE)
{
return0x45;
}
if(dwRead==0)
{
return0x50;
}
}
如果ReadFile()返回爲TRUE,則表明讀文件已經完成.但這種情況幾乎不會出現,因爲對外設的讀寫相對於內存的讀寫來說非常慢,所以一般在ReadFile()函數還沒執行完畢,程序已經執行到下一個語句.
當ReadFile()返回爲FALSE時,需要採用GetLastError()函數判斷讀操作是否在後臺進行.如果在後臺進行,則調用GetOverlappedResult()函數獲取ReadFile()函數的結果.在這裏要注意的是,GetOverlappedResult()函數的最後一個參數必須設置爲TRUE,表明要等ReadFile()函數在後臺運行完畢才返回.如果最後一個參數設置爲FALSE,則即使ReadFile()還在後臺執行,GetOverlappedResult()函數也會立刻返回,從而造成判斷錯誤.
另一種是調用WaitForSingleObject函數達到此目的:
if(ReadFile(hCom,szBuf,MAX_READ_BUFFER,&dwRead,olRead)==FALSE)
{
if(GetLastError()!=ERROR_IO_PENDING)
{
return0x40;
}
if(WaitForSingleObject(olRead.hEvent,INFINITE)!=WAIT_OBJECT_0)
{
return0x55;
if(GetOverlappedResult(hCom,olRead,&dwRead,FALSE)==FALSE)
{
return0x45;
}
if(dwRead==0)
{
return0x50;
}
}
因爲ReadFile()在後臺執行完畢以後,會發送一個event,所以在這裏可以調用WaitForSingleObject()等待ReadFile()執行完畢,然後再調用GetOverlappedResult()獲取ReadFile()的最終結果.在這裏需要注意的是,GetOverlappedResult()的最後一個參數一定要設置爲FALSE,因爲WaitForSingleObject()已經捕獲了ReadFile()發出的event,再也沒有event傳遞到GetOverlappedResult()函數.如果此時GetOverlappedResult()最後一個參數設置爲TRUE,則線程會一直停留在GetOverlappedResult()函數而不往下執行.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章