轉:http://blog.csdn.net/zcube/article/details/8591972
可到http://download.csdn.net/detail/zcube/5079651下載排版好的PDF版本。
原文鏈接:http://msdn.microsoft.com/en-us/library/ms810467.aspx
艾倫戴夫
微軟Windows開發者支持中心
1995/12/11
利用于:
摘要:學習Win32中的串口通訊和16位Windows操作系統中的串口通訊有很大的不同。這篇文章假定讀者已熟習Win32下多線程和同步的基本原理。另外,如果對Win32中的heap功能如果有基礎的了解,將使讀者在完全理解這篇文章中提到的多線程TTY(MTTTY)例子的內存管理方法上是很有用的。
Win32中的串口通訊和16位Windows中的串口通訊有顯著的不同。那些熟習16位串口通訊函數的開發人員將不能不重新學習許多系統部份的知識,以便能編寫正確的串口通訊程序。這篇文章將幫助實現這個目標。那些不熟習串口通訊的人員將發現這篇文章會為他們以后研究發展奠定堅實的基礎。
這篇文章假定讀者已熟習Win32下多線程和同步的基本原理。另外,如果對Win32中的heap功能如果有基礎的了解,將使讀者在完全理解這篇文章中提到的MTTTY例子的內存管理方法上是很有用的。
關于這些函數的更多信息,請查閱平臺SDK文檔:微軟Win32知識庫或微軟開發者聯機文庫。雖然那些控制用戶界面特性的利用程序接口(APIs)和對話框在這里其實不討論,但是對完全理解這篇文章所提供的例程還是很有用的。不熟習1般的Windows編程的讀者在開始處理串行通訊前首先應當學習1些Windows編程基礎。換句話說,冒失地潛水前先沾濕你的腳。
這篇文章主要介紹利用程序接口(APIs)和微軟Windows NT和Windows 95所兼容的方法。因此,只討論在NT和95這兩個平臺上都被支持的APIs。Windows 95支持Win32電話API(TAPI),但是Windows NT3.x卻不支持。因此,這里不對TAPI進行討論。但是,TAPI值得1提的時,它在調制解調器的連接和調用控制上是非常好的工具。如果1個利用程序產品觸及調制解調器工作和電話撥號,那末利用TAPI接口可以實現這些功能。它允許和用戶可能有的TAPI程序實現無縫結合。另外,這篇文章也不討論Win32中的1些配置函數,像GetCommProperties。
這篇文章包括的例子,MTTTY:多線程TTY(4918.exe),實現了許多這里所要討論的功能。在它的實現中使用了3個線程:1個用戶界面線程實現內存管理、1個寫線程實現控制所有的寫操作、還有1個讀/狀態線程實現讀數據和處理端口上產生改變的狀態。該例子采取1些不同的數據堆實現內存管理。它也廣泛使用同步方法增進線程之間的通訊。
使用CreateFile函數可以打開1個通訊端口。調用CreateFile打開通訊端口有兩種方式:堆疊的和非堆疊的。下面是使用堆疊方式打開1個通訊資源的例子:
在Win32軟件開發工具包(SDK)程序員參考手冊(概述,窗口管理,系統服務)中規定,當打開1個通訊端口時候,調用CreateFile有以下要求:
需要注意1件事,慣例上它們有4個端口分別為:COM1、COM2、COM3和COM4。Win32 API沒有提供任何途徑去肯定系統中存在的端口。Windows NT和Windows 95在配置串口方面相互其實不相同,所以任何1種方法都不能確保對所有的Win 32平臺都是可移植的。1些系統乃至有比慣例上的最大數量4個端口還要多的端口。硬件廠商和串行裝備驅動的作者可以用他們所喜歡的方式去自由命名端口。為此,如果用戶可以去指定他們想用的端口名是最好的選擇。如果1個端口不存在,在企圖打開這個端口的時候1個毛?。?span style="font-family:'Times New Roman'">ERROR_FILE_NOT_FOUND)將會出現,應當正告用戶這個端口是不可用的。
從通訊端口讀和寫在Win32中極為類似于Win32中文件的輸入/輸出(I/O)。實際上,實現文件I/O的函數和用于串行I/O的函數是相同的。Win32中的I/O可以通過兩種方式被使用:堆疊和非堆疊。在Win32 SDK文檔中用異步和同步這樣的術語去暗示這些I/O操作的類型。但是,這篇文章中將用堆疊和非堆疊這樣的術語。
非堆疊I/O對大多數開發者來講是熟習的,由于這屬于傳統的I/O操作情勢,當函數返回的時候1個被要求的操作將被假定為已完成。就堆疊I/O來講,即便操作沒有完成系統也能夠立刻返回給調用者,當操作完成的時候將會用信號通知調用者。程序可以利用I/O要求和結束這段時間去履行1些“后臺”工作。
在Win32中和16位Windows中對串行通訊端口的讀和寫有顯著的不同。16位Windows只有ReadComm和WriteComm函數。Win32中的讀寫操作可能牽涉更多的函數和選擇。這些問題在下文將會被討論。
非堆疊I/O非常簡單,雖然它還有1些限制。1個操作的履行將致使調用它的線程被阻塞。1旦該操作完成,函數返回,線程繼續工作。這類類型的I/O對多線程利用程序來講是非常有用的,由于當I/O操作的時候即便1個線程被阻塞,其它線程依然可以履行工作。利用程序有責任正確無誤的處理連續的端口操作。如果1個線程被阻塞去等待I/O操作的完成,隨后的其它線程如果調用1個通訊API也將可能被阻塞,直到最初的操作完成。例如,如果1個線程正在等待ReadFile函數返回,其它線程如果調用WriteFile函數將會被阻塞。
在非堆疊和堆疊操作之間做出選擇的諸多因素中,其中之1是要斟酌到可移植性。堆疊操作不是1個好的選擇,由于大多數操作系統其實不支持它。但是大多數操作系統支持多線程,所以多線程的非堆疊I/O操作從可移植性上面斟酌的話是最好的選擇。
堆疊I/O不像非堆疊I/O那樣簡單的,但是提供了更多的靈活性和效力。當1個端口打開的時候,對堆疊操作來講,允許線程與此同時履行I/O操作和其它的工作,即便這個操作正處于不肯定狀態。另外,堆疊操作允許單線程發出許多不同的要求和履行后臺工作,即便這個操作處于不肯定狀態。
對單線程和多線程利用程序,在發出要求和得到結果之間必須產生1些同步性。1個線程將會被阻塞,直到1個操作的結果變成有效的。堆疊I/O的優勢所在是允許1個線程在要求和完成之間去做1些工作。如果沒有工作可以被做,然后對堆疊I/O只有1種可能,就是它將允許為更好的用戶提供響應。
堆疊I/O是MTTTY例子中所使用的1種操作類型。它創建1個線程來負責讀取端口的數據和狀態。它也履行定期的后臺工作。程序創建另外1個線程專門用來從端口寫出數據。
注意:有時利用程序創建太多的線程,濫用多線程操作系統。雖然利用多線程可以解決很多困難的問題,但是創建過量的線程在利用程序中其實不是最有效的方式。在系統中線程沒有進程緊張,但是依然會占用系統資源,像CPU時間和內存。如果1個利用程序創建過量的線程,可能對全部系統的性能產生不利的影響。線程的1個更好的使用方式是對每一個工作類型創建1個不同的要求隊列,有1個工作者線程通過發出1個I/O要求使其進入要求隊列。上述方法將會被這篇文章中所提到的MTTTY這個例子用到。
1個堆疊I/O操作包括兩部份:創建操作和檢測是不是完成。創建操作必須建立1個OVERLAPPED結構體,為同步創建1個手工重置事件,然后在調用特定的函數(ReadFile或WriteFile)。I/O操作可能也可能不會立即的完成。對1個程序來講,如果假定1個堆疊操作要求總是產生1個堆疊操作是毛病的。如果1個操作完成后,利用程序需要準備繼續正常地運行。堆疊操作的第2部份是檢測它是不是完成。檢測操作是不是完成包括等待事件處理,檢查堆疊結果和處理數據。有很多工作牽涉到堆疊操作的緣由是存在很多故障點。如果1個非堆疊操作失敗了,函數只是會返回1個毛病返回的結果。如果1個堆疊操作失敗了,它可能在創建操作時候失敗或是使操作處于等待狀態。你也可能有1個超時操作或只是1個超時去等待操作完成的信號。
ReadFile函數將產生1個讀的操作。ReadFileEx也產生1個讀操作,但是由于它在Windows 95上是不可用的,所以在這篇文章中不對它做討論。這里的代碼段詳細說明了怎樣產生1個讀操作。注意,如果ReadFile函數返回TRUE,它的功能是將調用1個函數去處理數據。如果操作變成了堆疊方式,這個處理數據的函數也是1樣的。注意在代碼段中定義的fWaitingOnRead標記變量,用它來指明1個讀操作是不是為堆疊的。它通經常使用來避免在1個讀操作還沒有完成的時候又重新創建1個新的讀操作。
堆疊操作的第2部份是檢測它是不是完成。OVERLAPPED結構體中的事件句柄會被傳遞到WaitForSingleObject函數中進行等待,直到對象被傳遞信號。1旦事件被傳遞信號,則代表操作完成了。這其實不意味著操作是被成功地完成,僅僅是完成而已。GetOverlappedResult函數將報告操作的結果。如果產生了毛病,GetOverlappedResult函數將返回FALSE,GetLastError函數將返回毛病代碼。如果操作被成功的完成,GetOverlappedResult將返回TRUE。
注意:GetOverlappedResult函數可以探測操作是不是完成,也能夠返回操作的失敗狀態。如果操作沒有完成,GetOverlappedResult將返回FALSE且GetLastError函數將返回ERROR_IO_INCOMPLETE。另外,GetOverlappedResult可能會被阻塞直到操作完成。實際上,堆疊操作轉變成非堆疊操作可以通過給GetOverlappedResult函數中的bWait參數傳遞TRUE來實現。
這里的代碼段展現了1種檢查1個堆疊讀操作是不是完成的方法。注意下面的代碼也調用的處理數據函數和上面是相同的,該函數在操作完成后立即調用。也要注意使用的fWaitingOnRead標記,在這里它用來控制是不是履行檢測代碼,由于它只有在1個讀操作還沒有完成時候才應當被調用。