WinCE虛擬串口驅動(一)

//========================================================================
  //TITLE:
  //    WinCE虛擬串口驅動(一)
  //AUTHOR:
  //    norains
  //DATE:
  //    Saturday 28-March-2009
  //Environment:
  //    WINDOWS CE 5.0
  //========================================================================
 
  用過串口進行開發的朋友應該都知道,串口驅動是一個典型的獨佔設備。簡單點來說,就是在成功地調用CreateFile打開串口之後,沒有通過CloseHandle進行關閉,是無論如何都不能再次調用CreateFile來再次打開相同的串口。
 
  有的朋友可能會覺得莫名奇妙,爲什麼微軟要在這上面做限制呢?但其實從另一個角度來講,微軟這麼做是非常有道理的。以接收數據爲例子,在驅動裏面會有一定的緩存,用來保留一定量的數據。當通過ReadFile來獲取數據時,驅動就會將緩存給清空,然後再繼續接收數據。如果串口不是獨佔設備,可以多次打開,那麼在讀取數據上面就會有問題:應該什麼時候才清空緩存?比方說,其中一個線程通過ReadFile來獲得了數據,那麼驅動應不應該將緩衝清空?如果清空,那另一個線程也想獲得同樣的數據進行分析,那就會產生數據丟失;如果不清空,萬一之前已經通過ReadFile獲取數據的線程再次進行讀取,那麼它將會得到同樣重複的數據。如果想要在這多個進程中維持數據的同步,肯定要額外增加相應的標識,但這樣就會加大了驅動的複雜度,並且也無法和別的驅動保持一致。因此,微軟對串口實行獨佔設備的策略,是非常正確的。
 
  但,正確並不代表放之四海而皆準,在某些特殊的情況下,我們還是需要非獨佔性質的串口。簡單地舉個例子,在手持PND GPS設備中,導航軟件肯定是必須要能通過串口進行數據獲取來定位;可另一方面,我的另一個應用程序又想獲得GPS數據進行系統時間的校準。在這情形之下,我們就必須使用一個非獨佔性質的串口設備。
 
  爲了簡化設計,該串口設備的驅動我們約定如下:
 
  1.同一時間只能有一個進程對外輸出數據,其餘進程只能在該進程輸出完畢之後才能進行。
 
  2.程序不應該主動調用ReadFile來輪詢獲取數據。而是通過WaitCommEvent進行檢測,當返回的狀態中具備EV_RXCHAR時才調用ReadFile。並且該調用必須在一定的時間間隔之內,而且爲了不丟失數據,緩衝大小一定要等於或大於READ_BUFFER_LENGTH。
 
  之所以有如上約束,完全是出於設計簡便考慮。
 
 
  非獨佔式串口驅動主要是處理數據的分發,可以和具體的硬件分開,換句話說,該驅動是基於原有的串口驅動之上,實際上並“沒有”該設備,因此我們將該非獨佔式串口稱之爲“虛擬串口驅動”。這樣設計的優勢很明顯,可以不用理會具體的硬件規格,只要採用的是WinCE系統,並且原來已經具備了完善的串口驅動,那麼該虛擬串口驅動就能工作正常。
 
 
  接下來我們來看看該虛擬串口的具體實現。
 
  麻雀雖小,五官俱全,雖然說該驅動是“虛擬”的,但畢竟還是“驅動”,該有的部分我們還是要具備的。
 
  驅動的前綴爲VSP,取自於Virtual Serial Port之意。
 
  該驅動必須實現如下函數:

view plaincopy to clipboardprint?
VSP_Close  
VSP_Deinit  
VSP_Init  
VSP_IOControl  
VSP_Open  
VSP_PowerDown  
VSP_PowerUp  
VSP_Read  
VSP_Seek  
VSP_Write        
VSP_Close
VSP_Deinit
VSP_Init
VSP_IOControl
VSP_Open
VSP_PowerDown
VSP_PowerUp
VSP_Read
VSP_Seek
VSP_Write  

  因爲串口驅動是流設備,又和具體的電源管理五官,故VSP_Seek,VSP_PowerDown,VSP_PowerUp這些函數可以不用處理,直接返回即可。
 
 
  現在來看一下VSP_Open函數。
 
  VSP_Open函數我們大致需要如下流程處理事情:
 
  1.判斷當前的是否已經打開串口,如果已經打開,直接跳到4.
 
  2.獲取需要打開的串口序號,並打開該串口。如果打開失敗,直接跳到5.
 
  3.打開數據監視進程(注:該部分在數據讀取部分進行分析)。
 
  4.標識記數(即g_uiOpenCount)增加1。
 
  5.函數返回
 
 
  流程1:
 
  全局變量g_uiOpenCount用來保存打開的記數,所以只要判斷該數值是否爲0即可確定是否應該打開串口:
view plaincopy to clipboardprint?
if(g_uiOpenCount != 0)  
{         
goto SET_SUCCEED_FLAG;  

if(g_uiOpenCount != 0)
{  
goto SET_SUCCEED_FLAG;
}


  流程2:
 
  爲了讓程序更具備靈活性,所打開的串口序號我們不直接在驅動中設定,而是通過讀取註冊表的數值獲得:
view plaincopy to clipboardprint?
if(reg.Open(REG_ROOT_KEY,REG_DEVICE_SUB_KEY) == FALSE)  
{  
    RETAILMSG(TRUE,(TEXT("[VSP]:Failed to open the registry/r/n")));  
    goto LEAVE_CRITICAL_SECTION;  
}  
          
//Get the MAP_PORT name   
reg.GetValueSZ(REG_MAP_PORT_NAME,&vtBuf[0],vtBuf.size()); 
if(reg.Open(REG_ROOT_KEY,REG_DEVICE_SUB_KEY) == FALSE)
{
 RETAILMSG(TRUE,(TEXT("[VSP]:Failed to open the registry/r/n")));
 goto LEAVE_CRITICAL_SECTION;
}
  
//Get the MAP_PORT name 
reg.GetValueSZ(REG_MAP_PORT_NAME,&vtBuf[0],vtBuf.size());


  接下來便是打開具體的串口:

view plaincopy to clipboardprint?
g_hCom = CreateFile(&vtBuf[0],GENERIC_READ | GENERIC_WRITE ,0,NULL,OPEN_EXISTING,0,NULL);  
if(g_hCom == INVALID_HANDLE_VALUE )  
{  
    RETAILMSG(TRUE,(TEXT("[VSP]Failed to map to %s/r/n"),&vtBuf[0]));  
    goto LEAVE_CRITICAL_SECTION;  
}  
else 
{  
    RETAILMSG(TRUE,(TEXT("[VSP]Succeed to map to %s/r/n"),&vtBuf[0]));  
}    
g_hCom = CreateFile(&vtBuf[0],GENERIC_READ | GENERIC_WRITE ,0,NULL,OPEN_EXISTING,0,NULL);
if(g_hCom == INVALID_HANDLE_VALUE )
{
 RETAILMSG(TRUE,(TEXT("[VSP]Failed to map to %s/r/n"),&vtBuf[0]));
 goto LEAVE_CRITICAL_SECTION;
}
else
{
 RETAILMSG(TRUE,(TEXT("[VSP]Succeed to map to %s/r/n"),&vtBuf[0]));


  流程3:
 
  創建進程來監視數據:
view plaincopy to clipboardprint?
InterlockedExchange(reinterpret_cast<LONG *>(&g_bExitMonitorProc),FALSE);  
CloseHandle(CreateThread(NULL,NULL,MonitorCommEventProc,NULL,NULL,NULL)); 
InterlockedExchange(reinterpret_cast<LONG *>(&g_bExitMonitorProc),FALSE);
CloseHandle(CreateThread(NULL,NULL,MonitorCommEventProc,NULL,NULL,NULL));


  流程4:
 
  成功打開記數
view plaincopy to clipboardprint?
SET_SUCCEED_FLAG:     
    g_uiOpenCount ++;  
    bResult = TRUE; 
SET_SUCCEED_FLAG: 
 g_uiOpenCount ++;
 bResult = TRUE;

 

  流程5:
 
  函數返回:
view plaincopy to clipboardprint?
LEAVE_CRITICAL_SECTION:       
    LeaveCriticalSection(&g_csOpen);      
    return bResult;  
LEAVE_CRITICAL_SECTION:  
 LeaveCriticalSection(&g_csOpen); 
 return bResult; 

 

  和VSP_Open密切對應的是VSP_Close,該函數流程基本和VSP_Open相反處理:
 
  1.打開記數(g_uiOpenCount)減1。如果g_uiOpenCount爲不爲0,跳轉3。
 
  2.退出監視數據進程,並且關閉打開的串口。
 
  3.函數返回。
 
 
  流程1和流程2處理如下:
view plaincopy to clipboardprint?
g_uiOpenCount --;     
if(g_uiOpenCount == 0)  
{         
    //Notify the monitor thread to exit.      
    InterlockedExchange(reinterpret_cast<LONG *>(&g_bExitMonitorProc),TRUE);  
    DWORD dwMask = 0;  
    GetCommMask(g_hCom,&dwMask);  
    SetCommMask(g_hCom,dwMask);       
              
    while(InterlockedExchange(reinterpret_cast<LONG *>(&g_bMonitorProcRunning),TRUE) == TRUE)  
    {  
        Sleep(20);  
    }  
    InterlockedExchange(reinterpret_cast<LONG *>(&g_bMonitorProcRunning),FALSE);  
              
    CloseHandle(g_hCom);  
    g_hCom = NULL;  

g_uiOpenCount --; 
if(g_uiOpenCount == 0)
{  
 //Notify the monitor thread to exit. 
 InterlockedExchange(reinterpret_cast<LONG *>(&g_bExitMonitorProc),TRUE);
 DWORD dwMask = 0;
 GetCommMask(g_hCom,&dwMask);
 SetCommMask(g_hCom,dwMask);  
   
 while(InterlockedExchange(reinterpret_cast<LONG *>(&g_bMonitorProcRunning),TRUE) == TRUE)
 {
  Sleep(20);
 }
 InterlockedExchange(reinterpret_cast<LONG *>(&g_bMonitorProcRunning),FALSE);
   
 CloseHandle(g_hCom);
 g_hCom = NULL;
}

我們必須確保VSP_Open和VSP_Close中的某一個必須要全部處理完才能再次調用,否則在處理過程中如果又再次調用本函數或相對應的加載或卸載函數,那麼一定會引發我們不可預料的情況,所以我們在這兩個函數中增加了關鍵段,以維持處理上的同步:
view plaincopy to clipboardprint?
EnterCriticalSection(&g_csOpen);  
...  
LeaveCriticalSection(&g_csOpen); 
EnterCriticalSection(&g_csOpen);
...
LeaveCriticalSection(&g_csOpen);


  其餘的接口,算起來最簡單的是VSP_Write,只要確定同一時間只能有唯一的一個進程進行輸出即可:
view plaincopy to clipboardprint?
EnterCriticalSection(&g_csWrite);  
DWORD dwWrite = 0;  
WriteFile(g_hCom,pBuffer,dwNumBytes,&dwWrite,NULL);  
LeaveCriticalSection(&g_csWrite); 
EnterCriticalSection(&g_csWrite);
DWORD dwWrite = 0;
WriteFile(g_hCom,pBuffer,dwNumBytes,&dwWrite,NULL);
LeaveCriticalSection(&g_csWrite);

 

    在完成VSP_Read之前,我們先來看另外一個函數:WaitCommEvent。這是串口驅動特有的,目的是有某些時間發生時,能夠第一時間激活線程。該函數和驅動的MMD層有關,是MDD層的應用程序級別接口。具體串口的PDD層,WaitCommEvent函數體內也僅僅是調用了COM_IOControl接口,然後傳入IOCTL_SERIAL_WAIT_ON_MASK控制碼而已。也就是說,調用WaitCommEvent的代碼,就相當於如此調用COM_IOControl:
view plaincopy to clipboardprint?
DeviceIoControl(hCom,  
                                IOCTL_SERIAL_WAIT_ON_MASK,  
                                    NULL,  
                                    0,  
                                    pOutBuf,  
                                    dwOutBufLen,  
                                    &dwReturn,  
                                    NULL); 
DeviceIoControl(hCom,
        IOCTL_SERIAL_WAIT_ON_MASK,
         NULL,
         0,
         pOutBuf,
         dwOutBufLen,
         &dwReturn,
         NULL);

 

  換句話說,如果想讓虛擬串口驅動支持WaitCommEvent函數,我們只需要在VSP_IOControl處理IOCTL_SERIAL_WAIT_ON_MASK控制碼即可:
view plaincopy to clipboardprint?
BOOL VSP_IOControl(  
   DWORD dwHandle,  
   DWORD dwIoControlCode,  
   PBYTE pBufIn,  
   DWORD dwBufInSize,  
   PBYTE pBufOut,  
   DWORD dwBufOutSize,  
   PDWORD pBytesReturned  
   )  
{  
    ...  
      
    switch(dwIoControlCode)   
    {  
        ...  
                  
        case IOCTL_SERIAL_WAIT_ON_MASK:  
                      
            ...                   
            break;  
              
        ...  
    }  
}  
         
BOOL VSP_IOControl(
   DWORD dwHandle,
   DWORD dwIoControlCode,
   PBYTE pBufIn,
   DWORD dwBufInSize,
   PBYTE pBufOut,
   DWORD dwBufOutSize,
   PDWORD pBytesReturned
   )
{
 ...
 
 switch(dwIoControlCode) 
 {
  ...
    
  case IOCTL_SERIAL_WAIT_ON_MASK:
     
   ...     
   break;
   
  ...
 }
}
  

 

  推而廣之,像SetCommState,SetCommTimeouts等串口特有的函數,都僅僅只是對COM_IOControl函數進行的一層封裝而已。
 
  我們再回到WaitCommEvent函數。可能有的朋友直接認爲,我們只要在IOCTL_SERIAL_WAIT_ON_MASK段直接簡單調用原有的WaitCommEvent即可:
view plaincopy to clipboardprint?
switch(dwIoControlCode)   
{  
    ...  
              
    case IOCTL_SERIAL_WAIT_ON_MASK:  
    {                 
        //直接調用原生的WaitCommEvent,但實際是錯誤的  
        if(dwBufOutSize < sizeof(DWORD) || WaitCommEvent(g_hCom,reinterpret_cast<DWORD *>(pBufOut),NULL) == FALSE)  
        {  
            *pBytesReturned = 0;              
            return FALSE;  
        }  
        else 
        {  
            *pBytesReturned = sizeof(DWORD);  
            return TRUE;  
        }                     
    }  
                  
    ...  

switch(dwIoControlCode) 
{
 ...
   
 case IOCTL_SERIAL_WAIT_ON_MASK:
 {    
  //直接調用原生的WaitCommEvent,但實際是錯誤的
  if(dwBufOutSize < sizeof(DWORD) || WaitCommEvent(g_hCom,reinterpret_cast<DWORD *>(pBufOut),NULL) == FALSE)
  {
   *pBytesReturned = 0;   
   return FALSE;
  }
  else
  {
   *pBytesReturned = sizeof(DWORD);
   return TRUE;
  }     
 }
    
 ...
}

 

 但實際上這樣是不行的。查看文檔關於WaitCommEvent函數的描述,注意事項中有這麼一條:Only one WaitCommEvent can be used for each open COM port handle. This means that if you have three threads in your application and each thread needs to wait on a specific comm event, each thread needs to open the COM port and then use the assigned port handle for their respective WaitCommEvent calls.

 
  也就是說,WaitCommEvent只能被一個線程調用。如果多線程都同時調用該函數,會發生什麼情況呢?經過實際測試,如果多線程都調用相同的WaitCommEvent,那麼在某個線程調用WaitCommEvent時,之前已經有其餘的線程通過調用該函數進行等待狀態的話,那等待的線程立馬會喚醒。簡單點來說,就是同一時間只能有唯一的一個線程通過WaitCommEvent函數進入等待狀態。所以,對於IOCTL_SERIAL_WAIT_ON_MASK控制碼,我們不能簡單地調用WaitCommEvent函數。
 
  在這裏我們採用這麼一種設計,對於IOCTL_SERIAL_WAIT_ON_MASK的處理,我們是通過調用WaitForSingleObject進行線程等待。而虛擬串口驅動,會額外開放一個線程,該線程主要是通過調用WaitCommEvent來獲取原生串口的狀態,當狀態有通知時,再發送event給等待的線程。因此,對於IOCTL_SERIAL_WAIT_ON_MASK控制碼的處理可以所作如下:
view plaincopy to clipboardprint?
switch(dwIoControlCode)   
{  
    ...  
                  
    case IOCTL_SERIAL_WAIT_ON_MASK:  
    {                 
        if(dwBufOutSize < sizeof(DWORD) ||   WaitForSingleObject(g_hEventComm,INFINITE) == WAIT_TIMEOUT)  
                {  
                    *pBytesReturned = 0;              
                    return FALSE;  
                }  
                else 
                {  
                    InterlockedExchange(reinterpret_cast<LONG *>(pBufOut),g_dwEvtMask);  
                    *pBytesReturned = sizeof(DWORD);                          
                    return TRUE;  
                }                     
            }  
                  
            ...  
        } 
switch(dwIoControlCode) 
{
 ...
    
 case IOCTL_SERIAL_WAIT_ON_MASK:
 {    
  if(dwBufOutSize < sizeof(DWORD) ||  WaitForSingleObject(g_hEventComm,INFINITE) == WAIT_TIMEOUT)
    {
     *pBytesReturned = 0;   
     return FALSE;
    }
    else
    {
     InterlockedExchange(reinterpret_cast<LONG *>(pBufOut),g_dwEvtMask);
     *pBytesReturned = sizeof(DWORD);      
     return TRUE;
    }     
   }
    
   ...
  }


  驅動額外的等待線程所做如是:
view plaincopy to clipboardprint?
DWORD MonitorCommEventProc(LPVOID pParam)  
{             
    ...  
              
    while(TRUE)  
    {     
        DWORD dwEvtMask = 0;  
        BOOL bWaitRes = WaitCommEvent(g_hCom,&dwEvtMask,NULL);                
                  
        if(g_bExitMonitorProc != FALSE)  
        {  
            break;  
        }                     
                  
        if(bWaitRes == FALSE)  
        {  
            continue;  
        }         
                  
        ...  
              
        InterlockedExchange(reinterpret_cast<LONG *>(&g_dwEvtMask),dwEvtMask);  
        PulseEvent(g_hEventComm);         
                  
        ...  
                  
    }  
              
    ...  
              
    return 0;  

DWORD MonitorCommEventProc(LPVOID pParam)
{   
 ...
   
 while(TRUE)
 { 
  DWORD dwEvtMask = 0;
  BOOL bWaitRes = WaitCommEvent(g_hCom,&dwEvtMask,NULL);    
    
  if(g_bExitMonitorProc != FALSE)
  {
   break;
  }     
    
  if(bWaitRes == FALSE)
  {
   continue;
  }  
    
  ...
   
  InterlockedExchange(reinterpret_cast<LONG *>(&g_dwEvtMask),dwEvtMask);
  PulseEvent(g_hEventComm);  
    
  ...
    
 }
   
 ...
   
 return 0;
}

  現在是到考慮ReadFile實現的時候了。我們需要考慮到,不同進程,在同時讀取數據時,應該能獲得相同的數據。但對於原生的串口驅動,如果再次調用ReadFile,所獲得的數據絕對是不會和之前的一樣,否則就亂套了。於是,和IOCTL_SERIAL_WAIT_ON_MASK一樣,我們這麼也不能粗暴簡單地調用原生的ReadFile完事。
 
  我們轉換個思維,對於“不同進程,在同時讀取數據時,應該能獲得相同的數據”,我們應該是這麼理解:“不同進程,相當短的間隔內讀取數據,應該能獲得相同的數據”。如果要做到這點,我們只需要設置一個讀取緩存,當上級程序想要獲取數據時,我們只需要簡單地將數據返回即可。那麼接下來最關鍵的是,我們應該什麼時候讀取數據?什麼時候該刷新緩存呢?
 
  分開來說,最簡單的方式,就是在監視進程MonitorCommEventProc中讀取數據並刷新緩存。因爲該線程會調用WaitCommEvent函數進行等待,它能夠充分知道什麼時候有數據進來。只要有數據進來,我們就進行讀取。如果之前的緩存已經被讀取過,我們就清空緩存,存入新的數據;否則就在舊緩存之後添加我們新的數據。故此,完善的MonitorCommEventProc實現就應該如此:

view plaincopy to clipboardprint?
DWORD MonitorCommEventProc(LPVOID pParam)  
{  
    InterlockedExchange(reinterpret_cast<LONG *>(&g_bMonitorProcRunning),TRUE);  
      
    RETAILMSG(TRUE,(TEXT("[VSP]:MonitorCommEventProc Running!/r/n")));  
      
    std::vector<BYTE> vtBufRead(g_vtBufRead.size(),0);          
    while(TRUE)  
    {     
        DWORD dwEvtMask = 0;  
        BOOL bWaitRes = WaitCommEvent(g_hCom,&dwEvtMask,NULL);                
          
        if(g_bExitMonitorProc != FALSE)  
        {  
            break;  
        }                     
          
        if(bWaitRes == FALSE)  
        {  
            continue;  
        }         
          
        DWORD dwRead = 0;             
        if(dwEvtMask & EV_RXCHAR)  
        {  
            EnterCriticalSection(&g_csRead);                      
              
            ReadFile(g_hCom,&g_vtBufRead[0],vtBufRead.size(),&dwRead,NULL);       
            if(dwRead == vtBufRead.size() || g_bReaded != FALSE)  
            {  
                g_dwLenReadBuf = dwRead;  
                g_vtBufRead.swap(vtBufRead);  
            }  
            else if(dwRead != 0)  
            {  
                if(g_dwLenReadBuf + dwRead <= g_vtBufRead.size())  
                {  
                    g_dwLenReadBuf += dwRead;  
                    g_vtBufRead.insert(g_vtBufRead.end(),vtBufRead.begin(),vtBufRead.begin() + dwRead);  
                }  
                else 
                {  
                    DWORD dwCover = g_dwLenReadBuf + dwRead - g_vtBufRead.size();  
                    std::copy(g_vtBufRead.begin() + dwCover,g_vtBufRead.begin() + g_dwLenReadBuf,g_vtBufRead.begin());  
                    std::copy(vtBufRead.begin(),vtBufRead.begin() + dwRead,g_vtBufRead.begin() + (g_dwLenReadBuf - dwCover));  
                    g_dwLenReadBuf = g_vtBufRead.size();  
                }  
            }  
              
            g_bReaded = FALSE;  
              
            DEBUGMSG(TRUE,(TEXT("[VSP]:Read data : %d/r/n"),dwRead));     
          
            LeaveCriticalSection(&g_csRead);  
        }  
      
        if(dwEvtMask == EV_RXCHAR && ((g_dwWaitMask & EV_RXCHAR) == 0 || dwRead == 0))  
        {  
            //The return event mask is only EV_RXCHAR and there is not EV_RXCHAR in the wait mask.  
            continue;  
        }  
      
        InterlockedExchange(reinterpret_cast<LONG *>(&g_dwEvtMask),dwEvtMask);  
        PulseEvent(g_hEventComm);         
          
        //Sleep for other thread to respond to the event  
        Sleep(100);  
          
        DEBUGMSG(TRUE,(TEXT("[VSP]:PulseEvent! The event-mask is 0x%x/r/n"),dwEvtMask));      
          
    }  
      
    RETAILMSG(TRUE,(TEXT("[VSP]:Exit the MonitorCommEventProc/r/n")));    
    InterlockedExchange(reinterpret_cast<LONG *>(&g_bMonitorProcRunning),FALSE);  
      
    return 0;  

DWORD MonitorCommEventProc(LPVOID pParam)
{
 InterlockedExchange(reinterpret_cast<LONG *>(&g_bMonitorProcRunning),TRUE);
 
 RETAILMSG(TRUE,(TEXT("[VSP]:MonitorCommEventProc Running!/r/n")));
 
 std::vector<BYTE> vtBufRead(g_vtBufRead.size(),0);  
 while(TRUE)
 { 
  DWORD dwEvtMask = 0;
  BOOL bWaitRes = WaitCommEvent(g_hCom,&dwEvtMask,NULL);    
  
  if(g_bExitMonitorProc != FALSE)
  {
   break;
  }     
  
  if(bWaitRes == FALSE)
  {
   continue;
  }  
  
  DWORD dwRead = 0;   
  if(dwEvtMask & EV_RXCHAR)
  {
   EnterCriticalSection(&g_csRead);     
   
   ReadFile(g_hCom,&g_vtBufRead[0],vtBufRead.size(),&dwRead,NULL);  
   if(dwRead == vtBufRead.size() || g_bReaded != FALSE)
   {
    g_dwLenReadBuf = dwRead;
    g_vtBufRead.swap(vtBufRead);
   }
   else if(dwRead != 0)
   {
    if(g_dwLenReadBuf + dwRead <= g_vtBufRead.size())
    {
     g_dwLenReadBuf += dwRead;
     g_vtBufRead.insert(g_vtBufRead.end(),vtBufRead.begin(),vtBufRead.begin() + dwRead);
    }
    else
    {
     DWORD dwCover = g_dwLenReadBuf + dwRead - g_vtBufRead.size();
     std::copy(g_vtBufRead.begin() + dwCover,g_vtBufRead.begin() + g_dwLenReadBuf,g_vtBufRead.begin());
     std::copy(vtBufRead.begin(),vtBufRead.begin() + dwRead,g_vtBufRead.begin() + (g_dwLenReadBuf - dwCover));
     g_dwLenReadBuf = g_vtBufRead.size();
    }
   }
   
   g_bReaded = FALSE;
   
   DEBUGMSG(TRUE,(TEXT("[VSP]:Read data : %d/r/n"),dwRead)); 
  
   LeaveCriticalSection(&g_csRead);
  }
 
  if(dwEvtMask == EV_RXCHAR && ((g_dwWaitMask & EV_RXCHAR) == 0 || dwRead == 0))
  {
   //The return event mask is only EV_RXCHAR and there is not EV_RXCHAR in the wait mask.
   continue;
  }
 
  InterlockedExchange(reinterpret_cast<LONG *>(&g_dwEvtMask),dwEvtMask);
  PulseEvent(g_hEventComm);  
  
  //Sleep for other thread to respond to the event
  Sleep(100);
  
  DEBUGMSG(TRUE,(TEXT("[VSP]:PulseEvent! The event-mask is 0x%x/r/n"),dwEvtMask)); 
  
 }
 
 RETAILMSG(TRUE,(TEXT("[VSP]:Exit the MonitorCommEventProc/r/n"))); 
 InterlockedExchange(reinterpret_cast<LONG *>(&g_bMonitorProcRunning),FALSE);
 
 return 0;
}

  正因爲讀取是如此實現,所以我們纔有文章開頭的第二點約定:
 
  程序不應該主動調用ReadFile來輪詢獲取數據。而是通過WaitCommEvent進行檢測,當返回的狀態中具備EV_RXCHAR時才調用ReadFile(如果一直採用ReadFile來輪詢接收數據,很可能會讀取重複的數據)。並且該調用必須在一定的時間間隔之內(如果間隔太久,很可能因爲緩存已經刷新,數據丟失),而且爲了不丟失數據,緩衝大小一定要等於或大於READ_BUFFER_LENGTH(因爲只要讀取一次數據,讀取的標識就會被設置,當有新數據到達時,會刷新緩存,導致數據丟失)。
 
  這也同時解釋了MonitorCommEventProc進程爲何在PulseEvent之後會調用Sleep函數進行短暫的休眠,其作用主要是讓驅動的讀取進程歇歇,好讓上級等待進程能在等待事件返回時有足夠的時間來讀取獲得的數據。


本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/norains/archive/2009/03/28/4032257.aspx

發佈了15 篇原創文章 · 獲贊 13 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章