基於多線程技術和自定義消息編程實現Windows 9x非同步串列通信 |
|
conundrum
尊榮會員 發表:893 回覆:1272 積分:643 註冊:2004-01-06 發送簡訊給我 |
基於多線程技術和自定義消息編程實現Windows 9x非同步串列通信 http://www.gjwtech.com/scomm/scppbuildwindthreadmessage.htm 張志明 李蓉豔 王 磊 龔建偉評論:本文直接從底層編程用的是C Builder,想瞭解C Builder中多線程技術和消息編程的讀者可以看一看。 摘 要 分析了基於Windows 95/98平臺上的非同步串列通信程式開發方法,並結合開發實踐,用C Builder語言實現了基於多線程技術和消息回應機制的非同步串列通信,給出編程的一般步驟和詳細解釋。 關鍵字 串列通信 多線程 消息 API 事件 1 前言 串列通信具有連接簡單、使用靈活方便、資料傳遞可靠等優點,在工業監控、資料獲取和即時控制系統中得到了廣泛應用。但由於Windows 95/98對系統底層操作採取了遮罩的策略,不允許用戶對硬體I/O口進行直接操作,進行串列通信只能通過調用API函數來完成;同時Windows 9x通過消息佇列驅動管理程式,DOS中斷服務常式在其下面也很難實現,且即時性和可靠性都得不到保證;通過基於線程和消息的多工處理編程可以有效地解決這一問題,且能提高資料傳輸的吞吐量和應用程式的可靠性。 Windows 9x支援基於線程的搶先式多工處理。進程(Process)是應用程式的執行實例,而線程(Thread)則是進程內部執行的路徑。每個進程至少有一個主線程,還可包括若干子線程,線程間獨立運行。從根本上說,線程是可由系統調度的一個最簡單的代碼單元,同一進程的每個線程有自己的一組CPU指令、一組CPU寄存器和一個堆疊,由Windows 9x分配CPU時間片,需要小心處理線程的同步問題。基於線程的多工使得同一程式的兩個或多個部分可以同時運行。一個多線程的應用程式實際上在其內部實現了多工擴展,為代碼賦予了並行執行的特性,因而可以執行某些即時性或隨機性很強的操作,提高對CPU的利用率,加快通信程式的資訊處理速度。 作業系統在給各個線程分配CPU時間片時,通過其本身的調度機制來評價各個活動線程的優先順序,優先執行優先順序別高的活動線程,掛起優先順序別低的活動線程;當活動線程優先順序別相同時,系統調度程式則以輪轉方式分配CPU時間片。在搶先式多工處理中,只要系統調度程式確定有一個優先順序別更高的線程準備運行,則系統立刻會將優先順序別低的線程掛起(即使處於運行狀態),而把CPU時間片分配給優先順序別高的線程。 Windows 9x系統提供的開放式通用功能增強介面Win32 API(應用編程介面)是一個複雜函數、消息的集合。Windows 9x下把對串口和其他通信設備的支援與基本輸入輸出驅動程式集成為一體,串口的打開、關閉、讀取和寫入所用的函數與操作檔的函數相同,系統通過被稱為設備控制塊DCB的資料結構對串列口和串口通信驅動程式進行配置。 2 串列通信的基本編程 串列通信編程的基本流程如圖1所示,首先調用API函數CreateFile( )打開並初始化需要操作的序列埠: 圖1串列通信編成的基本流程 HANDLE CreateFile (LPCTSTR lpFileName, /*要打開的通信串口名稱*/ DWORD wDesiredAccess, /*指定串口的訪問方式,一般設置為可讀可寫方式*/ DWORD dwShareMode, /*指定串口操作的共用模式,串口不能共用,所以只能設置為0*/ LPSECURITY_ATTRIBUTES lpSecurityAttributes, /*設置串口的安全屬性,由於Win9x不支援安全屬性,此項只 能設置為NULL*/ DWORD dwCreationDistribution, /*對於通信串口,創建方式只能為OPEN_EXISTING*/ DWORD dwFlagsAndAttributes, /*指定串口屬性與標誌,設置為FILE_FLAG_OVERLAPPED (重疊I/O操作),指定串口以非同步方式通信*/ HANDLE hTemplateFile /*對於串口通信必須設置為NULL*/ );成功打開串列口後,函數返回串口的控制碼。 串口設備屬性的配置由以下API函數完成:SetupComm( )設置串列通信埠的輸入和輸出緩衝區的大小;通過設備控制塊DCB修改和設置串口工作狀態的參數,如串列傳輸速率、資料位元、奇偶校驗位等通信參數,SetCommState( )將DCB結構中的內容寫入串口設置;另外,SetCommTimeouts( )設置串口讀寫操作的溢出時間。事件驅動I/O設備時用SetCommMask( )設置通信事件控制碼,WaitCommEvent( )則用來等待通信事件發生。 設置工作完成後串列通信可用ReadFile( )對串口進行讀操作,WriteFile( )對串口進行寫操作。 串列通信結束時調用函數CloseHandle( )來關閉CreateFile函數返回的串口控制碼。 (注:有關的函數可參考C Builder線上幫助手冊中的詳細資料。) 多線程的串口I/O通信編程中,將對串口的讀、寫操作視為同一進程的兩個不同任務,創建讀線程和寫線程分別完成對串口的讀、寫操作;線程間的協調和同步由事件Event和臨界區CriticalSection物件實現;由於非同步串列通信事件的隨機性和即時性,要求通信線程優先于主線程被處理,所以設置各線程的優先順序別如下:讀線程的優先順序>寫線程的優先順序>主線程的優先順序。 3 C Builder 3.0/4.0 對多線程編程的支援 (1)直接使用Win32 SDK中提供的API函數。如CreateThread,SetThreadPriority,ResumeThread,ExitThread等函數,編程較複雜,編程工作量大。 (2)利用C Builder 3.0/4.0 提供的TThread線程物件類。 線程物件TThread類封裝了多線程編程的常用方法和資料,大大簡化了多線程編程的工作難度,編程時只要從TThread派生自己的物件類並創建相應的實例即可,常用的重要屬性/方法有: FreeOnTerminate屬性:布林類型,當設置為True時,線上程結束運行時會強迫TThread物件類自動釋放本身,設置為False時,需要顯式地在程式碼中調用該物件類的析構函數來釋放其本身。缺省設置為False。 Priority屬性:設置該線程的優先順序別,C Builder定義了枚舉類型TThreadPriority (tpIdle,tpLowest,tpLower,tpNormal,tpHigher, tpHighest,tpTimeCritical)來表示線程的優先順序,缺省設置為tpNormal。 Terminated屬性:布林類型,線程結束標誌。值為True時表示線程結束,值為False時表示線程正在運行。需要在Execute函數中不斷對其檢查,為True則結束線程。 _fastcall TThread(bool CreateSuspended):物件類的構造函數,如果參數CreateSuspended為False,線程創建後自動調用Execute( )方法函數,立即啟動運行;如果CreateSuspended為True,線程以掛起狀態啟動。 virtual void_fastcall Execute(void):成員函數,創建線程的執行代碼部分,在派生類中必須重載該方法函數,在函數體內加入自己的程式碼,實現具體的功能。 void_fastcall Synchronize (TThread Method&Method):同步方法。執行時掛起線程本身而將控制權交給主線程,由主線程調用參數Method所指定的函數或過程,完成後控制權交還給調用Synchronize方法的線程。Synchronize方法能避免多線程之間的衝突,防止程式“鎖死”。 void_fastcall Terminate(void):調用此成員方法函數將終止線程的運行,並且自動置線程結束標誌Terminated值為True。 4 多線程編程實現非同步串列通信 利用Windows 9x的多線程編程技術,編程創建輔助線程即時監視串口通信狀態,並由串口通信監視線程根據通信狀態向主線程發送相應的消息,由主線程分析處理。多線程串列通信法的最大優點是程式對接收資料具有自主覺察能力,一旦輔助的通信監視線程查詢到資料已經發送到串列口上,輔助線程自動接收資料後,向主線程發送資料接收到的消息,應用程式可根據該消息來處理通信串口傳送過來的資料,並且採用通信監視線程還不佔用CPU時間。 實踐編程中從線程物件TThread類派生建立輔線程串口讀線程(TCommReadThread)和串口寫線程(TCommWriteThread),完成串口通信操作,分別用來監視和管理串口通信的輸入、輸出。其中讀線程從通信串口讀取資料並傳輸給主線程處理;寫線程將從主線程傳來的資料寫入通信串口輸出;主線程除完成串口通信資源的打開、參數配置以及關閉的工作外,還要完成讀/寫線程的創建及關閉、多線程的協調、資料的中間處理與前端的人機交互等工作。 圖2、圖3、圖4分別為程式結構流程圖、串口寫線程流程圖和串口讀線程流程圖。 圖2非同步串列通信程式流程 圖3寫線程流程圖 圖4讀線程流程圖 針對串列通信的特點,採用事件(Event)物件和臨界區(CriticalSection)物件來同步串列通信中各線程對通信埠和存儲區資料的訪問,避免引起多線程間的衝突和鎖死。事件物件的作用是告訴其他線程發生了某一特定事件。相關的API函數有CreateEvent( )創建事件物件,調用成功後用SetEvent( )和ResetEvent( )手工重置事件物件狀態,CloseHandle( )解除事件物件;WaitForSingleObject( )和WaitForMultipleObjects( )函數分別等待一個或多個特定事件的發生。臨界區物件的作用是保護主線程與讀/寫線程之間的共用資料,一次只允許一個線程有權訪問被保護的資料。InitializeCriticalSection( )初始化臨界區對象,DeleteCriticalSection( )刪除臨界區對象並釋放其所占記憶體,EnterCriticalSection( )和LeaveCriticalSection( )分別是進入和退出資料保護狀態。 線程結構結合各個物件含義具體解釋如下: (1)發送資料完成事件:寫線程建立並等待該事件發生。該事件由寫線程將主線程傳遞過來的資料從通信串口發送成功後產生。 (2)讀取資料完成事件:讀線程建立並等待該事件發生。讀線程監測通信串口狀態,從通信串口成功讀取資料並將資料送往主線程後發生。 (3)通信過程錯誤事件:讀線程建立並等待該事件發生。應用程式利用此事件監視通信過程中發生的錯誤事件,為此目的需要在串口參數設置時在函數BOOL SetCommMask(HANDLE hFile, DWORD dwEvt Mask)中設置hFile為串口控制碼,dwEvtMask=EV_ERR常量。 (4)寫線程/讀線程結束事件:分別在寫線程/讀線程的構造函數中建立並等待該事件發生。線上程執行期間調用Terminate( )函數終止線程時產生。 (5)臨界區對象:主線程和寫線程分別定義並建立,用來同步寫線程與主線程共用的串口發送資料。 寫線程等待寫資料完成事件和線程結束事件;讀線程等待讀數據完成事件、通信錯誤事件和線程結束事件。為實現進程內部即主線程與讀/寫線程之間的資料交換,自定義如下消息在相應的事件發生後發送給主線程作相應的消息處理。 (1)WM_COMM_WRITE:寫線程在成功發送資料後發送給主線程。 (2)WM_COMM_READ:讀線程在接收串口資料成功後向主線程發送,主線程收到此消息後可對接收到的資料進行後續處理。 (3)WM_COMM_ERROR:由讀/寫線程發送。其中lParam未用,wParam用於標誌消息的發送者,寫線程為0,讀線程為1。讀/寫線程在通信過程中發生錯誤時發送此消息給主線程,主線程掛起相應線程直至通信錯誤事件處理完畢。 主線程在運行時與寫線程共用序列埠發送資料記憶體單元,其共用的資料由CriticalSection臨界區物件來保護。寫線程在對共用資料進行訪問前先申請對臨界區的所有權,訪問完成後釋放所有權退出臨界區狀態,主線程在此期間不能修改共用資料,只能等待寫線程臨界區狀態結束後才可訪問共用資料;同樣,主線程修改串口發送資料時也由主線程臨界區物件加以保護。 5 結束語 本文已成功地應用于智慧大廈監控系統電子密碼門鎖網路監控分系統的實踐中。實踐證明,多線程編程實現串列通信對於近距離的RS232介面通信和遠距離的RS485介面通信都能取得良好的效果。 張志明(西北工業大學電子資訊工程學院 西安 710072) 李蓉豔(西北工業大學電子資訊工程學院 西安 710072) 王磊(同濟大學 上海 200092) 參考文獻 1,郝 傑, 崔曉東, 龔 惠等譯. BORLAND C BUILDER編程指南. 北京: 電子工業出版社, 1998 2,Borland C Builder 3.0/4.0 Online Help |
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |