(一)Windows API串口通信編程概述
Windows環境下的串口編程與DOS環境下的串口編程有很大不同。Windows環境下的編程的最大特征之一就是設備無關性,它通過設備驅動程序將Windows應用程序同不同的外部設備隔離。Windows封裝了Windows的通信機制,這種方式稱為通信API,Windows程序可以利用Windows通信API進行編程,不用對硬件直接進行操作。這種體系被稱為Windows開放式服務體系(WOSA,Windows Open Services Architectures)。
早期的Windows3.x與Windows 9x/NT/2000的通信API有很大不同,在16位的串行通信程序中,一般使用16位的Windows API通信函數。為使大家對串口通信有一全面的理解,下面簡單介紹一下16位的Windows API通信函數:
(1) 打開和關閉串口
OpenComm()//打開串口資源,並指定輸入、輸出緩沖區的大小(以字節計); CloseComm()//關閉串口;
例:
int idComDev; idComdev=OpenComm(“COM1”,1024,512); CloseComm(idComDev);
(2) 初始化串口
BuildCommDCB()、setCommState()填寫設備控制塊DCB,然后對已打開的串口進行參數配置,例:
DCB dcb; BuildCommDCB(〝COM1:2400,n,8,1〞,&dcb); SetCommState(&dcb);
(3) 對串口進行讀寫
ReadComm、WriteComm()對串口進行讀寫操作,即數據的接收和發送。例:
char *m_pReceive; int count; ReadComm(idComDev,m_pReceive,count); Char wr[30]; int count2; WriteComm(idComDev,wr,count2);
通過對以上的描述我們可以看出,16位以下的串口通信程序最大的特點就在於串口等外部設備的操作有自己特有的API函數。
Windows 9x/NT/2000中的API一般都支持32位的操作,因此又稱為Win32API。為了在上述系統中實現串行數據傳送,可以使用Win32通信API。Win32通信API基本上是一個串行端口API,不是很適合於局域網(LAN)通信。雖然在線路上發送數據之前,LAN通常將數據位串行化,這和窗口或調制解調器發送數據之前所作的工作一模一樣,但局域網使用的線路的位數通常比串口少,而且還使用與串口協議很少有類似之處的訪問、路由、安全性和糾錯協議。局域網通信所需要的協議層使得Win32通信API對於這些應用來說很不理想。因此,在網絡通信和連接方面,TCP/IP協議要比Win32通信API更適合一些。
Windows操作系統是一個可搶占式的操作系統,所以Windows應用程序常常有被別的程序搶占時間片的可能,因此Win32通信API也不能用於實時通信。實時通信的質量與時間密切相關。例如,數字化音頻數據是實時數據,因為話音的質量依賴於播放它的速率。在錄制音頻時,它就以某個速度被數字化了,該速度就是人們所熟知的采樣速率。聲音必須以相同的采樣率重放,否則聽起來就會太慢或太快。實際中的視頻播放,也不是實時播放,那僅僅是存放在緩沖中的那部分數據。因此,不需要許多協議層的交互式、非實時的通信可以采用Win32通信API來實現。Win32通信API把串口操作(以及並口等)和文件操作統一起來了,使用類似的操作來實現。
(二) Windows串口通信相關API函數
“工欲善其事,必先利其器”,這一節將從使用的角度出發,對和串口通信相關的32位的Windows API函數進行介紹,力圖使你們對其有個全面、准確的認識。
2.1 打開和關閉串口
1. 打開串口
在32位的Windows系統中,串口和其它通信設備是作為文件處理的。串口的打開、關閉、讀取和寫入所用的函數與操作文件的函數完全一致。
通信會話以調用CreateFile()開始。CreateFile()為讀訪問、寫訪問或讀寫訪問“打開”串口。按照Windows的通常做法,CreateFile()返回一個句柄,隨后在打開的端口的操作中使用CreateFile()函數非常復雜,復雜性的原因之一是它是通用的。可以使用CreateFile打開已存在的文件,創建新文件和打開根本就不是文件的設備,例如串口、並口和調制解調器。CreateFile()函數聲明如下:
HANDLE CreateFile
(
LPCTSTR lpszName,
DWORD fdwAccess,
DWORD fdwShareMode,
LPSECURITY_ATTRIBUTES lpsa,
DWORD fdwCreate,
DWORD fdwAttrsAndFlags,
HANDLE hTemplateFile
)
CreateFile函數中的參數解釋如下:
·lpszName:指定要打開的串口邏輯名,用字符串表示,如“COM1”和“COM2”分別表示串口1和串口2。
·fdwAccess:用來指定串口訪問的類型。與文件一樣,串口也是可以被打開以供讀取、寫入或者兩者兼有。
GENERIC_READ位讀取訪問打開端口,GENERIC_READ位寫訪問打開端口。這兩個常數定義如下:
const GENERIC_READ = 0x80000000h;
const GENERIC_WRITE = 0x40000000h;
用戶可以用邏輯操作將這兩個標識符連接起來,為讀/寫訪問權限打開端口。因為大部分串口通信都是雙向的,因此常常在設置中將兩個標識符連接起來使用。如:
fdwAccess = GENERIC_READ | GENERIC_WRITE;
·fdwShareMode:指定該端口的共享屬性。該參數是為那些由許多應用程序共享的文件提供的。對於不能共享的串口,它必須設置為0。這就是文件與通信設備之間的主要差異之一。如果在當前的應用程序調用CreateFile()時,另一個應用程序已經打開了串口,該函數就會返回錯誤代碼,原因是兩個應用程序不能共享一個端口。然而,同一個應用程序的多個線程可以共享由CreateFile()返回的端口句柄,並且根據安全性屬性設置,該句柄可以被打開端口的應用程序的子程序所繼承。
·Ipsa:引用安全性屬性結構(SECURITY_ARRTIBUTES),該結構定義了一些屬性,例如通信句柄如何被打開端口的應用程序的子程序所繼承。將該參數設置為NULL將為該端口分配缺省的安全性屬性。子應用程序所繼承的缺省屬性是該端口不能被繼承的。
安全屬性結構SECURITY_ARRTIBUTES結構聲明如下:
typedef struct_SECURITY_ARRTIBUTE
{
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ARRTIBUTE;
SECURITY_ARRTIBUTES結構成員nLength指明該結構的長度,lpSecurityDescriptor指向一個安全描述字符,bInheritHandle表明句柄是否能被繼承。
·fdwCreate:指定如果CreateFile()正在被已有的文件調用時應采取的動作。因為串口總是存在,fdwCreate必須設置成OPEN_EXISTING。該標志告訴Windows不用企圖創建新端口,而是打開已經存在的端口。OPEN_EXISTING常數定義為:
const OPEN_EXISTING = 3;
·fdwAttrsAndFlags:描述了端口的各種屬性。對於文件來說,有可能具有很多屬性,但對於串口,唯一有意義的設置是FILE_FLAG_OVERLAPPED。當創建時指定該設置,端口I/O可以在后台進行(后台I/O也叫異步I/O)。FILE_FLAG_OVERLAPPED常數定義如下:
const FILE_FLAG_OVERLAPPED = 0x40000000h
·hTemplateFile:指向模板文件的句柄,當端口處於打開狀態時,不使用該參數,因而必須置成0。
調用CreateFile()函數打開COM1串口操作的例子如下所示:
1 HANDLE hCom; 2 DWORD dwError; 3 hCom=CreateFile 4 ( 5 “COM1”, // 文件名 6 GENERIC_READ | GENERIC_WRITE, // 允許讀和寫 7 0, // 獨占方式 8 NULL, 9 OPEN_EXISTING, // 打開而不是創建 10 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // 重疊方式 11 NULL 12 ); 13 14 if(hCom = = INVALID_HANDLE_VALUE) 15 { 16 dwError=GetLastError(); // 處理錯誤 17 }
一旦端口處於打開狀態,就可以分配一個發送緩沖區和接收緩沖區,並且通過調用SetupComm()實現其它初始化工作。也可以不調用SetupComm()函數,Windows系統也會分配缺省的發送和接收緩沖區,並且初始化端口。但為了保證緩沖區的大小與實際需要的一致,最好還是調用該函數。SetupComm()函數聲明如下:
BOOL SetupComm ( HANDLE hFile, // 通信設備句柄 DWORD dwInQueue, // 輸入緩沖區大小 DWORD dwOutQueue // 輸出緩沖區大小 );
SetupComm()函數中各項含義說明如下:
·hFile: 由GreatFile()返回的指向已打開端口的句柄。
·dwInQueue和dwOutQueue: 接收緩沖區的大小和發送緩沖區的大小。這兩個定義並非是實際的緩沖區的大小,指定的大小僅僅是“推薦的”大小,而Windows可以隨意分配任意大小的緩沖區。Windows設備驅動程序可以獲得這兩個數據,並不直接分配大小,而使用來優化性能和避免緩沖區超限。
注意:當使用CreateFile()函數打開串口時:為實現調制解調器的排他性訪問,共享標識必須設為零;創建標識必須設為OPEN_EXISTING;模板句柄必須置為空。
2. 關閉串口
關閉串口比打開串口簡單得多,只需要調用CloseHandle()函數關閉由CreateHandle()函數返回得句柄即可。
CloseHandle函數聲明如下:
BOOL CloseHandle ( HANDLE hObject // 需關閉的設備句柄 );
使用串口時一般要關閉它,如果忘記關閉串口,串口就會始終處於打開狀態,其它應用程序就不能打開並使用串口了。
2.2 串口配置和串口屬性
Windows 9x/NT/2000中配置串口提供了比Windows的早期版本更為強大的功能,當然相應也更加復雜。CreateFile函數打開串口后,系統將根據上次打開串口時設置的值來初始化串口,可以集成上次打開操作后的數值,包括設備控制塊(DCB)和超時控制結構(COMMTIMEOUTS)。如果是首次打開串口,Windows操作系統就會使用缺省的配置。
1. 串口配置
Windows 9x/NT/2000使用GetCommState()函數獲取串口的當前配置,使用SetCommState()重新分配串口資源的各個參數。
GetCommState()函數聲明如下:
BOOL GetCommState ( HANDLE hFile, // 通信設備句柄 LPDCB lpDCB // 指向device-control block structure的指針 );
其中的參數說明如下:
·hFile:由CreateFile()函數返回的指向已打開串口的句柄。
·lpDCB:一個非常重要的結構—設備控制塊DCB ( Device Control Block )。
DCB結構的主要參數說明如下:
·DCBLength: 一字節為單位指定的DCB結構的大小。
·Baudrate: 用於指定串口設備通信的數據傳輸速率,它可以是實際的數據傳輸速率數值,也可以是下列數據之一:CBR_110, CBR_19200, CBR_300, CBR_38400, CBR_600, CBR_56000, CBR_1200, CBR_57600, CBR_2400, CBR_115200, CBR_4800, CBR_12800, CBR_9600, CBR_25600, CBR_14400。
·fBinary: 指定是否允許二進制。Win32API不支持非二進制傳輸,因此這個參數必須設置為TRUE,如果設置為FALSE則不能正常工作。
·fParity: 指定是否允許奇偶校驗,如果這個參數設置為TRUE,則執行奇偶校驗並報告錯誤信息。
·fOutxCtsFlow: 指定CTS是否用於檢測發送流控制。當該成員為TRUE,而CTS為OFF時,發送將被掛起,直到CTS置ON。
·fOutxDsrFlow: 指定DSR是否用於檢測發送流控制,當該成員為TRUE,而DSR為OFF時,發送將被掛起,直到DSR置ON。
·fDtrControl: 指定DTR流量控制,可以是表1中的任一值。
表1 DTR流量控制
值 |
功能描述 |
DTR_CONTROL_DISABLE |
禁止DTR線,並保持禁止狀態 |
DTR_CONTROL_ENABLE |
允許DTR線,並保持允許狀態 |
DTR_CONTROL_HANDSHAKE |
允許DTR握手,如果允許握手,則不允許應用程序使用EscapeCommFunction函數調整線路 |
·fDsrSensitivity: 指定通信驅動程序對DTR信號線是否敏感,如果該位置設為TRUE時,DSR信號為OFF,接收的任何字節將被忽略。
·fTXContinueOnXoff: 指定當接收緩沖區已滿,並且驅動程序已經發送出XoffChar字符時發送是否停止。當該成員為TRUE時,在接收緩沖區內接收到了緩沖區已滿的字節XoffLim,並且驅動程序已經發送出XoffChar字符終止接收字節之后,發送繼續進行。該成員為FALSE時,接收緩沖區接收到代表緩沖區已空的字節XonLim,並且驅動程序已經發送出恢復發送的XonChar字符后,發送可以繼續進行。
·fOutX: 該成員為TRUE時,接收到XoffChar之后停止發送,接收到XonChar之后發送將重新開始。
·fInX: 該成員為TRUE時,接收緩沖區內接收到代表緩沖區滿的字節XoffLim之后,XoffChar發送出去,接收緩沖區接收到代表緩沖區已空的字節XonLim之后,XonChar發送出去。
·fErrorChar: 當該成員為TRUE,並且fParity為TRUE時,就會用ErrorChar成員指定的字符來代替奇偶校驗錯誤的接收字符。
·fNull: 指明是否丟棄接收到的NULL( ASCII 0 )字符,該成員為TRUE時,接收時去掉空(零值)字節;反之則不丟棄。
表2 RTS 流量控制
值 |
功能描述 |
RTS_CONTROL_DISABLE |
打開設備時禁止RTS線,並保持禁止狀態 |
RTS_CONTROL_ENABLE |
打開設備時允許RTS線,並保持允許狀態 |
DTR_CONTROL_HANDSHAKE |
允許握手。在接收緩沖區小於半滿時將RTS 置為ON,在接收緩沖區超過3/4時將RTS置為OFF。如果允許握手,則不允許應用程序使用EscapeCommFunction函數調整線路 |
DTR_CONTROL_TOGGLE |
當發送的字節有效,將RTS置為 ON,發送完緩沖區的所有字節后, RTS置為OFF |
·fRtsControl: 指定 RTS 流量控制,可以取表2中的值。0值和DTR_CONTROL_HANDSHAKE等價。
·fAbortOnError: 如果發送錯誤,指定是否可以終止讀、寫操作。如果該位為TRUE,當發生錯誤時,驅動程序以出錯狀態終止所有的讀寫操作。只有當應用程序調用ClearCommError()函數處理后,串口才能接收隨后的通信操作。
·fDummy2: 保留的位,沒有使用。
·wReserved:沒有使用,必須為零。
·XonLim: 指定在XOFF字符發送之前接收到緩沖區中可允許的最小字節數。
·XoffLim: 指定在XOFF字符發送之前緩沖區中可允許的最小可用字節數
·ByteSize: 指定端口當前使用的數據位數。
·Parity: 指定端口當前使用的奇偶校驗方法。它的可能值如表3所示。
·StopBits: 指定串口當前使用的停止位數,可能值如表4所示。
表3 奇偶校驗方法
值 |
功能描述 |
EVENPARITY |
偶校驗 |
MARKPARITY |
標號校驗 |
NOPARITY |
無校驗 |
ODDPARITY |
奇校驗 |
SPACEPARITY |
空格效益 |
表4 停止位數描述
值 |
功能描述 |
ONESTOPBIT |
1位停止位 |
ONE5STOPBITS |
1.5位停止位 |
TWOSTOPBITS |
2位停止位 |
·XonChar: 指明發送和接收的XON字符值,它表明允許繼續傳輸。
·XoffChar: 指明發送和接收的XOFF字符值,它表示暫停數據傳輸。
·ErrorChar: 本字符用來代替接收到的奇偶校驗發生錯誤的字符。
·EofChar: 用來表示數據的結束。
·EvtChar: 事件字符。當接收到此字符的時候,會產生一個事件。
·wReserved1: 保留的位,沒有使用。
如果GetCommState()函數調用成功,則返回值不為零。若函數調用失敗,則返回值為零,如果想得到進一步的錯誤信息,可以調用GetLastError()函數來獲取。
GetLastError()函數也是Win32API函數,它的聲明如下:
DWORD GetLastError(VOID);
如果應用程序只需要修改一部分配置的時候,可以通過GetCommState()函數獲得當前的DCB結構,然后更改DCB結構中的參數,調用SetCommState()函數配置修改過的DCB來配置端口。SetCommState()函數聲明如下:
BOOL SetCommState ( HANDLE hFile, // 已打開的串口的句柄 LPDCB lpDCB // 指向DCB結構的指針 );
SetCommState()函數的第一參數hFile是由CreateFile()函數返回的已打開的串口的句柄,第二個參數也是指向DCB結構的。如果函數調用成功,則返回值不為零;若函數調用失敗,則返回值為零。出錯時可以調用GetLastError()函數獲得進一步的出錯信息。SetCommState()函數調用的DCB結構中的XonChar等價於XoffChar成員,則SetCommState()函數會調用失敗。
DCB最經常改變的參數是數據傳輸速率、奇偶校驗的方法以及數據位和停止位數。Windows為改變這些設置提供了BuildCommDCB函數,函數聲明如下:
BOOL BuildCommDCB ( LPCTSTR lpDef, // 設置的字符串 LPDCB lpDCB // 指向DCB結構的指針 );
BuildCommDCB()參數包含新設置的字符串和一個DCB結構的參數,該設置將提供給DCB結構。新設置的字符串與DOS系統或者Windows NT/2000系統中的Mode命令格式相同。如:
baud=1200 parity=N data=8 stop=1
這條語句將數據傳輸速率設置為1200bits/s,關閉奇偶校驗,數據位數設為8,停止位數設為1。與在DOS或Windows NT/2000系統中一樣,該字符串不包括串口的名稱,實際上這個函數並不改變端口的設置,因此沒有必要標識該串口,當然這個串口必須是有效的串口。新的設置只是簡單地拷貝到已提供好的DCB結構中,要使新設置生效,還必須調用SetCommState()函數。BuildCommDCB()支持老的和新的各種版本的Mode命令,缺省情況下,BuildCommDCB()函數禁止XON/XOFF和硬件流的控制。如果使用硬件流控制,則必須設置DCB結構的各個成員的值。如果這個函數調用成功,則返回值不為零。如果想得到進一步的錯誤信息,可以調用GetLastError()函數來獲取。
2. 緩沖區控制
Win32通信API除了提供SetupComm()函數實現初始化的緩沖區控制外,還提供了PurgeComm()函數和FlushFileBuffers()函數來進行緩沖區操作。
PurgeComm()函數的聲明如下:
BOOL PurgeComm ( HANDLE hFile, // 返回的句柄 DWORD dwFlags // 執行的動作 );
參數hFile指向由CreateFile函數返回的句柄,dwFlags表示執行的動作,這個參數可以是表表5中的任一個。參數hFile指向由CreateFile函數返回的句柄,可以調用GetLastError()函數獲得進一步的錯誤信息。
表5 停止位數和奇偶校驗位
值 |
描述 |
PURGE_TXABORT |
即使發送操作沒有完成,也終止所有的重疊發送操作,立即返回 |
PURGE_RXABORT |
即使接收操作沒有完成,也終止所有的重疊接收操作,立即返回 |
PURGE_TXCLEAR |
清除發送緩沖區 |
PURGE_RXCLEAR |
清除接收緩沖區 |
由上面的敘述可以看出,PurgeComm()函數可以在讀寫操作的同時,清空緩沖區。當應用程序在讀寫操作時調用PurgeComm()函數,不能保證緩沖區內的所有字符都被發送。如果要保證緩沖區的所有字符都被發送,應該調用FlushFileBuffer()函數。該函數只受流量控制的支配,不受超時控制的支配,它在所有的寫操作完成后才返回。
FlushFileBuffers()的函數聲明如下:
BOOL FlushFileBuffers ( HANDLE hFile // 函數打開的句柄 );
參數hFile指向由CreateFile函數打開的句柄,如果該函數調用成功,則返回值不為零;若函數調用失敗,則返回值為零。出錯時可以調用GetLastError()函數獲得進一步的出錯信息。
2.3 讀寫串口
利用Win32通信API讀寫串口時,既可以同步執行,也可以重疊(異步)執行。在同步執行時,函數直到操作完成后才返回。這意味着在同步執行時線程會被阻塞,從而導致效率降低。在重疊執行時,即使操作還未完成,調用的函數也會立即返回。費時的I/O操作在后台進行,這樣線程就可以做其它工作。例如,線程可以在不同的句柄上同時執行I/O操作,甚至可以在同一句柄上同時進行讀寫操作。“重疊”一詞的含義就在於此。
1. 讀串口操作
程序可以使用Win32API ReadFile()函數或者ReadFileEx()函數從串口中讀取數據。ReadFile()函數對同步或異步操作都支持,而ReadFileEx()只支持異步操作。這兩個函數都受到函數是否異步操作、超時操作等有關參數的影響和限定。
ReadFile()函數聲明如下:
BOOL ReadFile ( HANDLE hFile, // 指向標識的句柄 LPVOID lpBuffer, // 指向一個緩沖區 DWORD nNumberOfBytesToRead, // 讀取的字節數 LPDWORD lpNumberOfBytesRead, // 指向調用該函數讀出的字節數 LPOVERLAPPED lpOverlapped // 一個OVERLAPPED的結構 );
其中主要參數介紹如下:
·hFile:指向標識的句柄。對串口來說,就是由CreateFile函數返回的句柄。該句柄必須擁有GENERIC_READ的權限。
·lpBuffer:指向一個緩沖區,該緩沖區主要用來存放從串口設備中讀取的數據。
·nNumberOfBytesToRead:指定要從串口設備讀取的字節數。
·lpNumberOfBytesRead:指向調用該函數讀出的字節數。ReadFile()在讀操作前,首先將其設置為0。Windows NT/2000中當lpOverlapped沒有設置時,lpNumberOfBytesRead必須設置。當lpOverlapped設置時,lpNumberOfBytesRead可以不設置。這是可以調用GetOverlappedResult()函數獲取實際的讀取數值。Windows 9x中這個參數一定要設置。
·lpOverlapped:是一個OVERLAPPED的結構,該結構將在后面介紹。如果hFile以FILE_FLAG_OVERLAPPED方式常見,則需要此結構;否則,不需要此結構。
需要注意的是如果該函數因為超時而返回,那么返回值是TRUE。參數lpOverlapped 在操作時應該指向一個OVERLAPPED的結構,如果該參數為NULL ,那么函數將進行同步操作,而不管句柄是否是由 FILE_FLAG_OVERLAPPED 標志建立的。當ReadFile返回FALSE時,不一定就是操作失敗,線程應該調用GetLastError函數分析返回的結果。例如,在重疊操作時如果操作還未完成函數返回,那么函數就返回FALSE,而且GetLastError函數返回ERROR_IO_PENDING。
2. 寫串口操作
可以使用Win32API函數WriteFile() 或者WriteFileEx()向串口中寫數據。WriteFile()函數對同步或異步操作都支持,而WriteFileEx()只支持異步操作。這兩個函數都受到函數是否異步操作、超時操作等有關參數的影響和限定。
WriteFile()函數聲明如下:
BOOL WriteFile ( HANDLE hFile, // 指向標識的句柄 LPCVOID lpBuffer, // 指向一個緩沖區 DWORD nNumberOfBytesToWrite, // 指定要向串口設備寫入的字節數 LPDWORD lpNumberOfBytesWritten, // 指向調用該函數已寫入的字節數 LPOVERLAPPED lpOverlapped // 一個OVERLAPPED的結構 );
其中主要參數介紹如下:
·hFile:指向標識的句柄。對串口來說,就是由CreateFile函數返回的句柄。該句柄必須擁有GENERIC_WRITE的權限。
·lpBuffer:指向一個緩沖區,該緩沖區主要用來存放待寫入串口設備的數據。
·nNumberOfBytesToWrite:指定要向串口設備寫入的字節數。
·lpNumberOfBytesWritten:指向調用該函數已寫入的字節數。WriteFile()在寫操作前,首先將其設置為0。Windows NT/2000中當lpOverlapped沒有設置時,lpNumberOfBytesWritten必須設置。當lpOverlapped設置時,lpNumberOfBytesWritten可以不設置。這是可以調用GetOverlappedResult()函數獲取實際的讀取數值。Windows 9x中這個參數一定要設置。
·lpOverlapped:是一個OVERLAPPED的結構,該結構將在后面介紹。如果hFile以FILE_FLAG_OVERLAPPED方式常見,則需要此結構;否則,不需要此結構。
如果函數調用成功,則返回值不為零;若函數調用失敗,則返回值為零。調用GetLastError()函數可以獲得進一步的出錯信息。
3. 異步I/O操作
異步(重疊)I/O操作是指應用程序可以在后台讀或者寫數據,而在前台做其它事情,例如,用程序可以在開始時對10000個數據進行讀或寫操作,然后返回執行其它的操作;在讀寫完成后,Windows就會產生一個信號,應用程序得到這個信號,便可以進行其它的讀寫操作。
要使用OVERLAPPED的結構,CreateFile()函數的dwFlagsAndAttributes參數必須設為FILE_FLAG_OVERLAPPED標識,讀寫串口函數必須指定OVERLAPPED結構。異步I/O操作在Windows中使用廣泛。
OVERLAPPED結構類型聲明如下:
typedef struct_OVERLAPPED { // 0 DWORD Internal; DWORD InteralHigh; DWORD Offset; DWORD OffsetHigh; HANDLE hEvent; } OVERLAPPED;
其中主要參數如下:
·Internal:操作系統保留,指出一個和系統相關的狀態。當GetOverlappedResult()函數返回時,如果將擴展信息設置為 ERROR_IO_PENDING,該參數有效。
·InteralHigh:操作系統保留,指出發送或接收的數據長度,當GetOverlappedResult()函數返回值不為0時,該參數有效。
·Offset和OffsetHigh:指明文件傳送的開始位置和字節偏移量的高位字。當進行端口操作時,這兩個參數被忽略。
·hEvent:指定一個I/O操作完成后觸發的事件(信號)。在調用讀寫函數進行I/O操作之前,必須設置該參數。
在設置了異步I/O操作后,I/O操作和函數返回有以下兩種情況:
1 函數返回時I/O操作已經完成:此時結果好像是同步執行的,但實際上這是異步執行的結果。
2 函數返回時I/O操作還沒完成:此時一方面,函數返回值為零,並且GetLastError()函數返回ERROR_IO_PENDING;另一方面,系統把OVERLAPPED中的信號事件設為無信號狀態。當I/O操作完成時,系統要把它設置為信號狀態。
異步I/O操作可以由GetOverlappedResult()函數來獲取結果,也可以使用Windows信號函數來處理。GetOverlappedResult()函數可聲明為:
BOOL GetOverlappedResult
(
HANDLE hFile,
LPOVERLAPPED lpOverlapped,
LPDWORD lpNumberOfBytesTransferred,
BOOL bWait
);
其中主要參數介紹如下:
·hFile:標識通信句柄,它應該和開始調用重疊結構的ReadFile、WriteFile、WaitCommEvent函數的參數一致。
·lpOverlapped:在啟動異步操作時指定的OVERLAPPED結構。
·lpNumberOfBytesTransferred:指向一個長整型變量,該變量接收有一個讀或寫操作實際傳遞的字節數。
·bWait:指定函數是否等待掛起的異步操作完成。如果該參數設為1,則該函數知道操作完成后才返回。如果該參數被設為0,同時處於被掛起狀態,則該函數返回為0,並且GetLastError函數返回ERROR_IO_INCOMPLETE。
如果該函數調用成功,則返回值不為零;若函數調用失敗,則返回值為零。調用GetLastError()函數可以獲得進一步的出錯信息。
Windows也使用等待函數來檢查事件對象的當前狀態或等待Windows狀態信號,在WaitForSingleObject()函數, WaitForSingleObjectEx()函數,以及WaitForMultipleObject() ,WaitForMultipleObjectsEx() 函數中指定OVERLAPPED結構中的 hEvent,即可獲取函數返回事件。
4. 超時設置
Windows 9x/NT/2000中讀寫串口引入了超時結構。超時結構直接影響讀和寫的操作行為。當事先設定的超時間隔消逝時,ReadFile() 、ReadFileEx()、 WriteFile()和 WriteFileEx()操作仍未結束,那么超時設置將無條件結束讀寫操作,而不管是否已讀出或已寫入指定數量的字符。
在讀或寫操作期間發生的超時將不按錯誤處理,即讀或寫操作返回指定成功的值。對於同步讀或寫操作,實際傳輸的字節數由ReadFile()和Write()函數報告。對於異步操作,則有OVERLAPPED結構來獲取。
超時結構定義如下:
typedef struct_COMMTIMEOUTS
{
DWORD ReadIntervalTimeout;
DWORD ReadTotalTimeoutMultiplier;
DWORD ReadTotalTimeoutConstant;
DWORD WriteTotalTimeoutMultiplier;
DWORD WriteTotalTimeoutConstant;
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
其中主要參數如下:
·ReadIntervalTimeout:以ms為單位指定通信線路上兩個字符到達之間的最大時間間隔。在ReadFile()操作期間,從接收到第一個字符時開始計時。如果任意兩個字符到達之間的時間間隔超過這個最大值,則ReadFile()操作完成,並返回緩沖數據。如果被置為0,則表示不使用間隔超時。
·ReadTotalTimeoutMultiplier:以ms為單位指定一個系數,該系數用來計算讀操作的總超時時間。
·ReadTotalTimeoutConstant:以ms為單位指定一個常數,該常數也用來計算讀操作的總超時時間。
·WriteTotalTimeoutMultiplier:以ms為單位指定一個系數,該系數用來計算寫操作的總超時時間。
·WriteTotalTimeoutConstant:以ms為單位指定一個常數,該常數也用來計算寫操作的總超時時間。
超時有兩種類型。第一種類型叫區間超時(interval timeout),它僅適應於從端口讀取數據。它指定在讀取兩個字符之間要經歷多長時間。接收一個字符時,Windows就啟動一個內部計時器。在下一個字符到達之前,如果定時器超過了區間超時設定時間,讀函數就會放棄。第二種類型的超時叫做總超時( total timeout),它適於讀和寫端口。當讀或寫特定字節數需要的總時間超過某一閾值時,該超時即被觸發。
Windows使用下面的式子計算總超時時間:
ReadTotalTimeout=( ReadTotalTimeoutMultiplier*bytes_to_read )+ ReadTotalTimeoutConstant;
WriteTotalTimeout=( WriteTotalTimeoutMultiplier*bytes_to_write )+ WriteTotalTimeoutConstant;
該方程使總超時時間成為靈活的工具。總超時時間不是固定值,而是根據讀或寫的字節數而“漂浮不定”。應用程序通過設置系數為0而只是用常數,和通過設置常數為0而只使用於系數。如果系數和常數都為0,則沒有總超時時間。
因此每個讀操作的總超時時間等於ReadTotalTimeoutMultiplier參數值乘以讀操作要讀取的字節數再加上ReadTotalTimeoutConstant參數值的和。如果將ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant都設置為0,則表示讀操作不使用總超時時間。每個讀間隔超時參數ReadIntervalTimeout被設置為MAXDWORK,而且兩個讀總超時參數都被設置為0,那么標識只要一讀完接收緩沖區而不管得到什么字符就完成讀操作,即使它是空的。當接收中有間隔時,間隔超時將迫使讀操作返回。因此使用間隔超時的進程可以設置一個非常短的間隔超時參數,這樣它可以實現對一個或一些字符的小的、孤立的數據作出反應。
每個寫操作的WriteTotalTimeoutConstant等於WriteTotalTimeoutMultiplier成員值乘以寫操作要寫的字節數,再加上WriteTotalTimeoutConstant參數值的和。如果WriteTotalTimeoutMultiplier和WriteTotalTimeoutConstant參數值都設置為0則表示寫操作不使用WriteTotalTimeoutConstant。
在傳輸某種流量控制被阻塞時和調用SetCommBreak()函數把字符掛起,寫操作的超時可能有用。如果所有的讀超時參數都為0,即沒有使用讀超時,那么讀操作知道讀完要求的字節數或發生錯誤時位置。同樣,如果所有的寫超時參數都為0,那么寫操作知道要求的字節數或發生錯誤時為止。當打開通信資源時,超時參數將根據上次設備被打開時所設置的值設置。如果資源從未打開或調用SetComm函數,那么所有的超時參數都設置為0。
如果欲獲得當前超時參數,應用程序可以調用GetCommTimeouts()函數。該函數聲明如下:
BOOL GetCommTimeouts
(
HANDLE hFile,
LPCOMMTIMEOUTS lpCommTimeouts
);
其中主要參數如下:
·hFile:標識通信設備,CreateFile()函數返回該句柄;
·lpCommTimeouts:指向一個CommTIMEOUTS結構,返回超時信息。
如果該函數調用成功,則返回值不為零;若函數調用失敗,則返回值為零。調用GetLastError()函數可以獲得進一步的出錯信息。
如果要設置或改變原來的超時參數,應用程序可以調用SetCommTimeouts()函數。該函數聲明如下:
BOOL SetCommTimeouts
(
HANDLE hFile,
LPCOMMTIMEOUTS lpCommTimeouts
);
其中主要參數如下:
·hFile:標識通信設備,CreateFile()函數返回該句柄;
·lpCommTimeouts:指向一個CommTIMEOUTS結構,返回超時信息。
如果該函數調用成功,則返回值不為零;若函數調用失敗,則返回值為零。調用GetLastError()函數可以獲得進一步的出錯信息。
5. 通信狀態和通信錯誤
如果在串口通信中發生錯誤,如發生中斷,奇偶錯誤等,I/O操作將會終止。如果程序要進一步執行I/O操作,必須調用ClearCommError()函數。ClearCommError()函數有兩個作用:第一個作用是清除錯誤條件;第二個作用是確定串口通信狀態。ClearCommError()函數的聲明如下:
BOOL ClearCommError
(
HANDLE hFile,
LPDWORD lpErrors,
LPCOMSTAT lpStat
);
其中主要參數介紹如下:
·hFile :標識通信設備,CreateFile()函數返回該句柄。
·lpErrors:指向用一個指明錯誤類型的掩碼填充的32位變量。該參數可以是表6中各值的組合。
·lpStat:指向一個COMSTAT結構,該結構接收設備的狀態信息。如果lpStat參數不設置,則沒有設備狀態信息被返回。
表6 通信錯誤列表
值 |
描述 |
CE_BREAK |
硬件檢測到一個中斷條件 |
CE_FRAME |
硬件檢測到一個幀出錯 |
CE_IOE |
發生I/O錯誤 |
CE_MODE |
模式出錯,或者是句柄無效 |
CE_OVERRUN |
超速錯誤 |
CE_RXOVER |
接收緩沖區超限,或者是輸入緩沖區中沒有空間,或者實在文件結束符(EOF)接收后接收到一個字符 |
CE_RXPARITY |
奇偶校驗錯誤 |
CE_TXFULL |
發送緩沖區滿 |
CE_DNS |
沒有檢測到並行設備 |
CE_OOP |
並行設備缺紙 |
CE_PTO |
並行設備發生超時錯誤 |
如果該函數調用成功,則返回值不為零;若函數調用失敗,則返回值為零。調用GetLastError()函數可以獲得進一步的出錯信息。在同步操作時,可以調用ClearCommError()函數來確定串口的接收緩沖區處於等待狀態的字節數,而后可以使用ReadFile()或者WriteFile()函數一次讀寫完。
COMSTAT結構存放有關通信設備的當前信息。該結構內容由ClearCommError()函數填寫。COMSTAT結構聲明如下:
typedef struct_COMSTAT ( DWORD fCtsHold: 1; DWORD fDsrHold: 1; DWORD fRlsdHold: 1; DWORD fXoffSent: 1; DWORD fEof: 1; DWORD fTxim: 1; DWORD fReserved: 25; DWORD cbInQue; DWORD cbOutQue; } COMSTAT,*LPCOMSTAT;
其中主要參數介紹如下:
·fCtsHold:指明是否等待CRS信號,如果為1,則發送等待。
·fDsrHold:指明是否等到DRS信號,如果為1,則發送等待。
·fRlsdHold:指明是否等待RLSD信號,如果為1,則發送等待。
·fXoffSent:指明收到XOFF字符后發送是否等待。如果為1,則發送等待。如果把XOFF字符發送給一系統時,該系統就把下一個字符當成XON,而不管實際字符是什么,此時發送將停止。
·fEof:EOF字符送出。
·fTxim:指明字符是否正等待被發送,如果為1,則字符正等待被發送。
·fReserved:系統保留。
·cbInQue:指明串行設備接收到的字節數。並不是指ReadFile操作要求讀的字節數。
·cbOutQue:指明發送緩沖區尚未發送的字節數。如果進行不重疊寫操作時值為0。
2.4 通信事件
Windows進程中監視發生在通信資源中的一組事件,這樣應用程序可以不檢查端口狀態就可以知道某些條件何時發生,這將是非常有用的。通過使用事件,應用程序不需要為接收字節而連續不斷地檢測端口,從而節省CPU時間。
1. 通信事件
Windows可以利用GetCommMask()函數和 SetCommMask函數來控制表7所示的通信事件。
表7 Windows通信事件列表
值 |
描述 |
EV_BREAK |
檢測到輸入的終止 |
EV_CTS |
CTS(清除發送)信號改變狀態 |
EV_DSR |
DSR(數據設置就緒)信號改變狀態 |
EV_ERR |
發生了線路狀態錯誤,線路狀態錯誤時CE_FRAME(幀錯誤)、CE_OVERRUN (接收緩沖區超限)和CE_RXPARITY(奇偶校驗錯誤) |
EV_RING |
檢測到振鈴 |
EV_RLSD |
RLSD(接收到線路信號檢測)信號改變狀態 |
EV_RXCHAR |
接收到一個字符,並放入輸入緩沖區 |
EV_RXFLAG |
接收到事件字符(DCB結構地EvtChar成員),並放入輸入緩沖區 |
EV_TXEMPTY |
輸出緩沖區中最后一個字符發送出去 |
2. 操作通信事件
應用程序可以利用SetCommMask()函數簡歷事件掩模來監視指定通信資源上的事件。SetCommMask函數的聲明如下:
BOOL SetCommMask
(
HANDLE hFile,
DWORD dwEvtMask
);
其中主要參數介紹如下:
·hFile :標識通信設備,CreateFile()函數返回該句柄。
·dwEvtMask:事件掩模,標識將被監視的通信事件。如果該參數設置為0,則表示禁止所有事件。如果不為0,則可以是表7中各種事件的組合。
如果該函數調用成功,則返回值不為零;若函數調用失敗,則返回值為零。調用GetLastError()函數可以獲得進一步的出錯信息。
如果想獲取特定通信資源的當前事件掩模,可以使用GetCommMask()函數。GetCommMask()函數聲明如下:
BOOL GetCommMask
(
HANDLE hFile,
LPDWORD lpEvtMask
);
其中主要參數介紹如下:
·hFile :標識通信設備,CreateFile()函數返回該句柄。
·dwEvtMask:事件掩模,標識將被監視的通信事件,一個32位變量,可以是表7中各種事件的組合。
如果該函數調用成功,則返回值不為零;若函數調用失敗,則返回值為零。調用GetLastError()函數可以獲得進一步的出錯信息。
3. 監視通信事件
在用SetCommMask()指定了有用的事件后,應用程序就調用WaitCommEvent()函數來等待其中一個事件發生。WaitCommEvent()函數既可以同步使用,也可以異步使用。WaitCommEvent()函數聲明如下:
BOOL WaitCommEvent
(
HANDLE hFile,
LPDWORD lpEvtMask,
LPOVERLAPPED lpOverlapped,
);
其中主要參數介紹如下:
·hFile :標識通信設備,CreateFile()函數返回該句柄。
·dwEvtMask:指向一個32位變量,接收事件掩模,標識所發生的通信事件屬於何種類型。可以是表7中各種事件的組合。
·lpOverlapped:指向一個OVERLAPPED結構,如果打開hFile表示的通信設備時,指定FILE_FLAG_OVERLAPPED標志,則該參數被忽略。如果不需要異步操作,則這個參數不用設置。
如果該函數調用成功,則返回值不為零;若函數調用失敗,則返回值為零。調用GetLastError()函數可以獲得進一步的出錯信息。
如果lpOverlapped參數不設置或打開hFile標識的通信設備是未指定FILE_FLAG_OVERLAPPED標志,則知道發生了指定時間或出錯時,WaitCommEvent()函數才返回。如果lpOverlapped參數指向一個OVERLAPPED結構,並且打開hFile標識的通信設備時指定了FILE_FLAG_OVERLAPPED標志,則WaitCommEvent()函數以異步操作實現。這種情況下,OVERLAPPED結構中必須含有一個人工復位事件的句柄。和所有異步函數一樣,如果異步操作不能立即實現,則該函數返回0,並且GetLastError()函數返回ERROR_IO_PENDING,以指示該操作正在后台進行。此時WaitCommEvent()函數返回之前,系統將OVERLAPPED結構中的hEvent參數設置為無信號狀態;當發生了指定時間或出錯時,系統將其設置為有信號狀態。調用程序可使用等待函數確定事件對象的狀態,然后使用GetOverlappedResult()函數確定WaitCommEvent()函數的操作結束。GetOverlappedResult()函數報告該操作成功或者失敗,並且lpEvtMask()函數所指向的變量被設置以指示所發生的事件。
如果一個進程在WaitCommEvent()函數操作進行期間使用SetCommMask()函數將立即返回,並且由EvtMask參數指向的變量被設置。
注意:WaitCommEvent()只檢測發生在等待開始后的事件。例如,如果指定EV_RXCHAR事件,則只有當收到函數字符並將字符放進接收緩沖區后才能滿足等待條件。WaitCommEvent()調用時已在接收緩沖區中的字符不符合等待條件。監視CTS、DSR等信號狀態改變的事件時,WaitCommEvent()函數只報告信號的變動,但不報告當前的信號狀態,如果要查詢這些信號狀態,進程可以調用GetCommModemstatus()函數。