Win32 串口编程(一)
翻譯自:ms-help://MS.MSDNQTR.v80.chs/MS.MSDN.v80/MS.WIN32COM.v10.en/dnfiles/html/msdn_serial.htm
老外寫的文章,雖比較全面,但很啰嗦,不如看各個函數的文檔來得快。為方便以后查閱,列出本文涉及的主要函數如下:
CreateFile、ReadFile、WriteFile、GetOverlappedResult、WaitForSingleObject
SetCommMask、WaitCommEvent
ClearCommError、GetCommModemStatus、EscapeCommFunction
GetCommState、BuildCommDCB、SetCommState、SetCommTimeouts
此外,新浪的博客系統限制文章最大長度為40000字節,只好把這篇文章分成幾部分了。
0 簡介
本文僅關注在Windows NT和95間兼容的API。Windows 95支持Telephony API(TAPI),但Windows NT 3.x不支持TAPI,所以本文不討論它。
本文的示例程序MTTTY(Multithreaded TTY)使用了三個線程:一個進行內存管理的界面線程;控制所有寫入操作的寫入者線程;讀取數據和處理端口狀態改變的讀取/狀態線程。示例采用了一些不同的堆來進行內存管理;還大量使用了同步方法來進行線程間通信。
1 打開端口
使用CreateFile函數打開端口。打開端口時有兩種方法:重疊的和非重疊的。下列代碼片段以重疊方式打開端口:
?
| HANDLE?hComm; hComm?=?CreateFile(?gszPort,?? ????????????????????GENERIC_READ?|?GENERIC_WRITE,? ????????????????????0,? ????????????????????0,? ????????????????????OPEN_EXISTING, ????????????????????FILE_FLAG_OVERLAPPED, ????????????????????0); if?(hComm?==?INVALID_HANDLE_VALUE) ???//?error?opening?port;?abort |
?
?
?
?
?
?
去掉參數中的FILE_FLAG_OVERLAPPED就是非重疊操作方式了。用CreateFile打開通信端口時,有下列限制:
- fdwShareMode 必須是0。通信端口不能像文件那樣被共享。要共享通信端口,需要使用句柄繼承或者復制操作。
- fdwCreate 必須指定 OPEN_EXISTING 標志。
- hTemplateFile 參數必須是 NULL。
端口名通常是COM1、COM2、COM3和COM4。Win32 API不提供確定系統中有哪些端口可用的機制。Windows NT和Windows 95跟蹤系統已安裝端口的方法是不同的,所以不太可能提供兼容的確定可用端口的方法。某些系統可能有多于4個端口,而傳統的通信端口最大個數是4。硬件廠商和串口驅動編寫者可以自由地為端口命名。所以,程序最好可以讓用戶指定要使用的端口名字。如果端口不存在,則試圖打開端口時會返回ERROR_FILE_NOT_FOUND錯誤,這時應該提示用戶端口不可用。
2 讀寫操作
通信端口的讀寫操作與文件I/O操作非常相似,它們使用同樣的函數。Win32的I/O操作可分為兩種:重疊(overlapped)的和非重疊的(nonoverlapped)。平臺SDK文檔分別使用異步(asynchronous)和同步(synchronous)來表示這兩種I/O方式。
很多開發者都熟悉非重疊I/O,因為它就是傳統的I/O方式:函數返回時,所請求的操作已經完成。然而在重疊I/O的情況下,系統則可能在操作還沒有完成的情形下立即返回,隨后才通知調用者操作完成。程序可以在發起I/O請求和請求被完成之間進行一些后臺工作。
2.1 非重疊I/O
非重疊I/O的工作方式很簡單:I/O操作進行時,調用線程被阻塞;操作完成后,函數返回,調用線程可以繼續執行。在多線程應用中,這種I/O方式很有用:一個線程阻塞在某I/O操作上時,其他線程可以繼續工作。應用程序應該保證對端口的串行訪問。某個線程阻塞在等待某I/O操作上時,其他線程后續的通信API調用也都將阻塞。比如說,一個線程在等待ReadFile調用返回時,另一個線程的WriteFile函數調用將阻塞。
在選擇使用非重疊還是重疊方式時,可移植性是要考慮的因素之一。有時候重疊操作并不是好的選擇,因為很多操作系統不支持它;然而很多操作系統都支持某種形式的多線程。所以從兼容性方面考慮,多線程非重疊I/O可能是最好的選擇。
2.2 重疊I/O
重疊I/O不像非重疊I/O那樣簡單易懂,但卻靈活高效。使用重疊方式打開的端口允許多個線程同時進行I/O操作,并且在操作進行期間可以進行其他的工作。此外,重疊操作的行為方式還允許單個線程提交多個不同的請求,然后在操作進行期間進行其他后臺工作。
在單線程和多線程應用中,都必須在提交I/O請求和處理操作結果間進行一些同步操作。線程可能需要在操作結果可用前阻塞;當然也可以進行其他工作。如果沒有其他需要進行的工作,則重疊I/O的優點是更好的用戶響應性能。
MTTTY使用了重疊I/O。它創建用于讀取數據和監測端口狀態的線程,并且還定時進行一些后臺工作;此外它還另外創建一個線程用于寫入數據。
重疊I/O操作分為兩個部分:創建I/O操作和檢測操作完成。創建I/O操作涉及到建立OVERLAPPED結構體、創建用于同步的手動復位事件、調用恰當的函數(ReadFile或者WriteFile)。I/O操作可能立即完成,也可能不能立即完成,不能認為一個重疊I/O操作請求總是生成一個重疊操作。如果操作立即完成,程序應該可以繼續進行通常的處理。檢測操作完成涉及到等待事件句柄、檢查操作完成結果、處理數據。與重疊I/O相關的工作更多的原因是有更多的失敗點。非重疊操作中,簡單地通過函數返回值判斷操作是否失敗;而重疊操作中,則可能在創建操作請求時失敗,或者操作阻塞期間失敗,也可能是操作超時,或者是等待操作完成信號超時。
2.2.1 讀操作
下面的代碼片段展示了提交重疊的讀操作請求的方法。注意,如果ReadFile返回TRUE,調用了一個函數處理數據。代碼定義了fWaitingOnRead標志,它表示是否有重疊的讀取操作存在,用于阻止在一個操作進行中時提交另一個讀取操作請求。
?
| DWORD?dwRead; BOOL?fWaitingOnRead?=?FALSE; OVERLAPPED?osReader?=?{0}; //?Create?the?overlapped?event.?Must?be?closed?before?exiting //?to?avoid?a?handle?leak. osReader.hEvent?=?CreateEvent(NULL,?TRUE,?FALSE,?NULL); if?(osReader.hEvent?==?NULL) ???//?Error?creating?overlapped?event;?abort. if?(!fWaitingOnRead)?{ ???//?Issue?read?operation. ???if?(!ReadFile(hComm,?lpBuf,?READ_BUF_SIZE,?&dwRead,?&osReader))?{ ??????if?(GetLastError()?!=?ERROR_IO_PENDING)?????//?read?not?delayed? ?????????//?Error?in?communications;?report?it. ??????else ?????????fWaitingOnRead?=?TRUE; ???} ???else?{???? ??????//?read?completed?immediately ??????HandleASuccessfulRead(lpBuf,?dwRead); ????} } |
?
OVERLAPPED結構體的事件句柄被傳遞給WaitForSingleObject以等待事件授信,操作完成。注意,事件受信表示操作完成,而不是操作成功完成。應該用GetOverlappedResult來取得操作結果,它返回TRUE表示操作成功完成;FALSE表示有錯誤發生,用GetLastError可以取得具體的錯誤碼。也可以用GetOverlappedResult來檢測操作完成:GetOverlappedResult返回FALSE,GetLastError返回ERROR_IO_INCOMPLETE表示操作進行中。如果對bWait參數傳入TRUE,則效果就是重疊操作變成了非重疊的,直到操作完成,函數才返回。下面的代碼片段展示了一種檢測重疊讀取操作完成的方法。注意fWaitingOnRead標志的使用,它是檢測代碼的控制入口,只有在某操作進行中時,才應該調用檢測代碼。
| #define?READ_TIMEOUT??????500??????//?milliseconds DWORD?dwRes; if?(fWaitingOnRead)?{ ???dwRes?=?WaitForSingleObject(osReader.hEvent,?READ_TIMEOUT); ???switch(dwRes) ???{ ??????//?Read?completed. ??????case?WAIT_OBJECT_0: ??????????if?(!GetOverlappedResult(hComm,?&osReader,?&dwRead,?FALSE)) ?????????????//?Error?in?communications;?report?it. ??????????else ?????????????//?Read?completed?successfully. ?????????????HandleASuccessfulRead(lpBuf,?dwRead); ??????????//??Reset?flag?so?that?another?opertion?can?be?issued. ??????????fWaitingOnRead?=?FALSE; ??????????break; ??????case?WAIT_TIMEOUT: ??????????//?Operation?isn't?complete?yet.?fWaitingOnRead?flag?isn't ??????????//?changed?since?I'll?loop?back?around,?and?I?don't?want ??????????//?to?issue?another?read?until?the?first?one?finishes. ??????????// ??????????//?This?is?a?good?time?to?do?some?background?work. ??????????break;??????????????????????? ??????default: ??????????//?Error?in?the?WaitForSingleObject;?abort. ??????????//?This?indicates?a?problem?with?the?OVERLAPPED?structure's ??????????//?event?handle. ??????????break; ???} } |
?
?
2.2.2 寫入操作
寫入操作跟讀取操作非常相似。下面的代碼片段展示了如何提交寫入操作,并等待操作完成。
?
| BOOL?WriteABuffer(char?*?lpBuf,?DWORD?dwToWrite) { ???OVERLAPPED?osWrite?=?{0}; ???DWORD?dwWritten; ???DWORD?dwRes; ???BOOL?fRes; ???//?Create?this?write?operation's?OVERLAPPED?structure's?hEvent. ???osWrite.hEvent?=?CreateEvent(NULL,?TRUE,?FALSE,?NULL); ???if?(osWrite.hEvent?==?NULL) ??????//?error?creating?overlapped?event?handle ??????return?FALSE; ???//?Issue?write. ???if?(!WriteFile(hComm,?lpBuf,?dwToWrite,?&dwWritten,?&osWrite))?{ ??????if?(GetLastError()?!=?ERROR_IO_PENDING)?{? ?????????//?WriteFile?failed,?but?isn't?delayed.?Report?error?and?abort. ?????????fRes?=?FALSE; ??????} ??????else ?????????//?Write?is?pending. ?????????dwRes?=?WaitForSingleObject(osWrite.hEvent,?INFINITE); ?????????switch(dwRes) ?????????{ ????????????//?OVERLAPPED?structure's?event?has?been?signaled.? ????????????case?WAIT_OBJECT_0: ?????????????????if?(!GetOverlappedResult(hComm,?&osWrite,?&dwWritten,?FALSE)) ???????????????????????fRes?=?FALSE; ?????????????????else ??????????????????//?Write?operation?completed?successfully. ??????????????????fRes?=?TRUE; ?????????????????break; ???????????? ????????????default: ?????????????????//?An?error?has?occurred?in?WaitForSingleObject. ?????????????????//?This?usually?indicates?a?problem?with?the ????????????????//?OVERLAPPED?structure's?event?handle. ?????????????????fRes?=?FALSE; ?????????????????break; ?????????} ??????} ???} ???else ??????//?WriteFile?completed?immediately. ??????fRes?=?TRUE; ???CloseHandle(osWrite.hEvent); ???return?fRes; } |
?
注意,上面的代碼使用WaitForSingleObject時,超時值是INFINITE,這使得函數無限等待直到操作完成。這可能讓調用線程似乎是被掛起了;而實際上只是寫入操作需要較長的時間,或者流控制阻塞了傳輸操作。下文將討論的狀態檢查可以檢測到這種情況,但它也不會讓WaitForSingleObject返回。有三種方法可以克服此問題:
- 把代碼放在單獨的線程中。這樣寫入線程在等待寫操作完成時,其他線程可以進行任何所需的操作。MTTTY就是這么做的。
- 使用COMMTIMEOUTS使得寫操作在經過一個超時值指定的時間后完成。本文后面的“通信超時”節將詳細討論它。MTTTY也可以使用這種方法。
- 修改WaitForSingleObject調用,使用超時值。這樣會更麻煩:如果原來的操作仍在進行中,程序提交另一個操作請求,則需要分配新的OVERLAPPED結構和重疊事件。這種記錄跟蹤保持是很困難的,尤其是與“工作隊列”相比較時。MTTTY使用了工作隊列。
上面代碼中的WaitForSingleObject使用了INFINITE作為超時值,其效果等同于使用TRUE作為GetOverlappedResult的fWait參數。下面是等效的更簡潔的代碼:
?
| BOOL?WriteABuffer(char?*?lpBuf,?DWORD?dwToWrite) { ???OVERLAPPED?osWrite?=?{0}; ???DWORD?dwWritten; ???BOOL?fRes; ???//?Create?this?writes?OVERLAPPED?structure?hEvent. ???osWrite.hEvent?=?CreateEvent(NULL,?TRUE,?FALSE,?NULL); ???if?(osWrite.hEvent?==?NULL) ??????//?Error?creating?overlapped?event?handle. ??????return?FALSE; ???//?Issue?write. ???if?(!WriteFile(hComm,?lpBuf,?dwToWrite,?&dwWritten,?&osWrite))?{ ??????if?(GetLastError()?!=?ERROR_IO_PENDING)?{? ?????????//?WriteFile?failed,?but?it?isn't?delayed.?Report?error?and?abort. ?????????fRes?=?FALSE; ??????} ??????else?{ ?????????//?Write?is?pending. ?????????if?(!GetOverlappedResult(hComm,?&osWrite,?&dwWritten,?TRUE)) ????????????fRes?=?FALSE; ?????????else ????????????//?Write?operation?completed?successfully. ????????????fRes?=?TRUE; ??????} ???} ???else ??????//?WriteFile?completed?immediately. ??????fRes?=?TRUE; ???CloseHandle(osWrite.hEvent); ???return?fRes; } |
?
GetOverlappedResult并不總是等待重疊操作完成的最好方法。比如說,如果應用需要同時等待另一個事件句柄,則第一個代碼片段模型比第二個更好,因為可以很容易地用WaitForMultipleObjects替換WaitForSingleObject,來等待更多的句柄。MTTTY就是這么做的。
在前一個重疊操作完成前重用OVERLAPPED結構是重疊I/O編程中常現的一個錯誤。如果要在前一個重疊操作完成前提交新的重疊操作請求,則需要分配新的OVERLAPPED結構,其hEvent字段也應該包含新的手動復位事件句柄。只有在重疊操作完成后,OVERLAPPED結構和其事件句柄才可以被重用。
串口通信中使用OVERLAPPED結構時只需要修改hEvent字段,其他字段只需要初始化為零。
總結
以上是生活随笔為你收集整理的Win32 串口编程(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Win32串口操作的技巧
- 下一篇: java sdk下载_Java Sdk下