一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

多線程與串行通信

 優(yōu)點點 2010-09-03
1 多任務(wù)、進程和線程  
    Windows是一個多任務(wù)操作系統(tǒng)。傳統(tǒng)的Windows 3.x只能依靠應(yīng)用程序之間的協(xié)同來實現(xiàn)協(xié)同式多任務(wù),而Windows 95/NT實行的是搶先式多任務(wù)。

在Win 32(95/NT)中,每一個進程可以同時執(zhí)行多個線程,這意味著一個程序可以同時完成多個任務(wù)。對于象通信程序這樣既要進行耗時的工作,又要保持對用戶輸入響應(yīng)的應(yīng)用來說,使用多線程是最佳選擇。當(dāng)進程使用多個線程時,需要采取適當(dāng)?shù)拇胧﹣肀3志€程間的同步。

  利用Win 32的重疊I/O操作和多線程特性,程序員可以編寫出高效的通信程序。在這一講的最后將通過一個簡單的串行通信程序,向讀者演示多線程和重疊I/O的編程技術(shù)。

1.1 Windows 3.x的協(xié)同多任務(wù)

在16位的Windows 3.x中,應(yīng)用程序具有對CPU的控制權(quán)。只有在調(diào)用了GetMessage、PeekMessage、WaitMessage或Yield后,程序才有可能把CPU控制權(quán)交給系統(tǒng),系統(tǒng)再把控制權(quán)轉(zhuǎn)交給別的應(yīng)用程序。如果應(yīng)用程序在長時間內(nèi)無法調(diào)用上述四個函數(shù)之一,那么程序就一直獨占CPU,系統(tǒng)會被掛起而無法接受用戶的輸入。

  因此,在設(shè)計16位的應(yīng)用程序時,程序員必須合理地設(shè)計消息處理函數(shù),以使程序能夠盡快返回到消息循環(huán)中。如果程序需要進行費時的操作,那么必須保證程序在進行操作時能周期性的調(diào)用上述四個函數(shù)中的一個。

  在Windows 3.x環(huán)境下,要想設(shè)計一個既能執(zhí)行實時的后臺工作(如對通信端口的實時監(jiān)測和讀寫),又能保證所有界面響應(yīng)用戶輸入的單獨的應(yīng)用程序幾乎是不可能的。

有人可能會想到用CWinApp::OnIdle函數(shù)來執(zhí)行后臺工作,因為該函數(shù)是程序主消息循環(huán)在空閑時調(diào)用的。但OnIdle的執(zhí)行并不可靠,例如,如果用戶在程序中打開了一個菜單或模態(tài)對話框,那么OnIdle將停止調(diào)用,因為此時程序不能返回到主消息循環(huán)中!在實時任務(wù)代碼中調(diào)用 PeekMessage也會遇到同樣的問題,除非程序能保證用戶不會選擇菜單或彈出模態(tài)對話框,否則程序?qū)⒉荒芊祷氐絇eekMessage的調(diào)用處,這將導(dǎo)致后臺實時處理的中斷。

  折衷的辦法是在執(zhí)行長期工作時彈出一個非模態(tài)對話框并禁止主窗口,在消息循環(huán)內(nèi)分批執(zhí)行后臺操作。對話框中可以顯示工作的進度,也可以包含一個取消按鈕以讓用戶有機會中斷一個長期的工作。典型的代碼如清單12.1所示。這樣做既可以保證工作實時進行,又可以使程序能有限地響應(yīng)用戶輸入,但此時程序?qū)嶋H上已不能再為用戶干別的事情了。


//清單12.1 在協(xié)同多任務(wù)環(huán)境下防止程序被掛起的一種方法

bAbort
=FALSE;
lpMyDlgProc
=MakeProcInstance(MyDlgProc, hInst);
hMyDlg
=CreateDialog(hInst, “Abort”, hwnd, lpMyDlgProc); //創(chuàng)建一個非模態(tài)對話框
ShowWindow(hMyDlg, SW_NORMAL);
UpdateWindow(hMyDlg);
EnableWindow(hwnd, FALSE); 
//禁止主窗口

while(!bAbort)
{
    
//執(zhí)行一次后臺操作        
    while(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))        
    {        
        
if(!IsDialogMessage(hMyDlg, &msg))            
        {        
            TranslateMessage(
&msg);        
            DispatchMessage(
&msg);
        }
    }    
}

EnableWindow(hwnd, TRUE); 
//允許主窗口
DestroyWindow(hMyDlg);
FreeProcInstance(lpMyDlgProc);

 

1.2 Windows 95/NT的搶先式多任務(wù)

  在32位的Windows系統(tǒng)中,采用的是搶先式多任務(wù),這意味著程序?qū)PU的占用時間是由系統(tǒng)決定的。系統(tǒng)為每個程序分配一定的CPU時間,當(dāng)程序的運行超過規(guī)定時間后,系統(tǒng)就會中斷該程序并把CPU控制權(quán)轉(zhuǎn)交給別的程序。與協(xié)同式多任務(wù)不同,這種中斷是匯編語言級的。程序不必調(diào)用象 PeekMessage這樣的函數(shù)來放棄對CPU的控制權(quán),就可以進行費時的工作,而且不會導(dǎo)致系統(tǒng)的掛起。
  例如,在Windows3.x 中,如果某一個應(yīng)用程序陷入了死循環(huán),那么整個系統(tǒng)都會癱瘓,這時唯一的解決辦法就是重新啟動機器。而在Windows 95/NT中,一個程序的崩潰一般不會造成死機,其它程序仍然可以運行,用戶可以按Ctrl+Alt+Del鍵來打開任務(wù)列表并關(guān)閉沒有響應(yīng)的程序。
1.3 進程與線程

  在32位的Windows系統(tǒng)中,術(shù)語多任務(wù)是指系統(tǒng)可以同時運行多個進程,而每個進程也可以同時執(zhí)行多個線程。
  進程就是應(yīng)用程序的運行實例。每個進程都有自己私有的虛擬地址空間。每個進程都有一個主線程,但可以建立另外的線程。進程中的線程是并行執(zhí)行的,每個線程占用CPU的時間由系統(tǒng)來劃分。
  可以把線程看成是操作系統(tǒng)分配CPU時間的基本實體。系統(tǒng)不停地在各個線程之間切換,它對線程的中斷是匯編語言級的。系統(tǒng)為每一個線程分配一個CPU時間片,某個線程只有在分配的時間片內(nèi)才有對CPU的控制權(quán)。實際上,在PC機中,同一時間只有一個線程在運行。由于系統(tǒng)為每個線程劃分的時間片很?。?0 毫秒左右),所以看上去好象是多個線程在同時運行。
  進程中的所有線程共享進程的虛擬地址空間,這意味著所有線程都可以訪問進程的全局變量和資源。這一方面為編程帶來了方便,但另一方面也容易造成沖突。
  雖然在進程中進行費時的工作不會導(dǎo)致系統(tǒng)的掛起,但這會導(dǎo)致進程本身的掛起。所以,如果進程既要進行長期的工作,又要響應(yīng)用戶的輸入,那么它可以啟動一個線程來專門負責(zé)費時的工作,而主線程仍然可以與用戶進行交互。
1.4 線程的創(chuàng)建和終止

  線程分用戶界面線程和工作者線程兩種。用戶界面線程擁有自己的消息泵來處理界面消息,可以與用戶進行交互。工作者線程沒有消息泵,一般用來完成后臺工作。

  MFC應(yīng)用程序的線程由對象CWinThread表示。在多數(shù)情況下,程序不需要自己創(chuàng)建CWinThread對象。調(diào)用AfxBeginThread函數(shù)時會自動創(chuàng)建一個CWinThread對象。

例如,清單12.2中的代碼演示了工作者線程的創(chuàng)建。AfxBeginThread函數(shù)負責(zé)創(chuàng)建新線程,它的第一個參數(shù)是代表線程的函數(shù)的地址,在本例中是MyThreadProc。第二個參數(shù)是傳遞給線程函數(shù)的參數(shù),這里假定線程要用到CMyObject對象,所以把pNewObject指針傳給了新線程。線程函數(shù)MyThreadProc用來執(zhí)行線程,請注意該函數(shù)的聲明。線程函數(shù)有一個32位的pParam參數(shù)可用來接收必要的參數(shù)。

清單12.2 創(chuàng)建一個工作者線程

//主線程

pNewObject 
= new CMyObject;
AfxBeginThread(MyThreadProc, pNewObject);
//新線程
UINT MyThreadProc( LPVOID pParam )
{
    CMyObject
* pObject = (CMyObject*)pParam;
        
if (pObject == NULL || !pObject->IsKindOf(RUNTIME_CLASS(CMyObject)))
            
return -1// 非法參數(shù)
        
// 用pObject對象來完成某項工作
        return 0// 線程正常結(jié)束
}

 

AfxBeginThread的聲明為:
CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );

參數(shù)pfnThreadProc是工作線程函數(shù)的地址。pParam是傳遞給線程函數(shù)的參數(shù)。nPriority 是線程的優(yōu)先級,一般是THREAD_PRIORITY_NORMAL,若為0,則使用創(chuàng)建線程的優(yōu)先級。nStackSize說明了線程的堆棧尺寸,若為0則堆棧尺寸與創(chuàng)建線程相同。dwCreateFlags指定了線程的初始狀態(tài),如果為0,那么線程在創(chuàng)建后立即執(zhí)行,如果為 CREATE_SUSPENDED,則線程在創(chuàng)建后就被掛起。參數(shù)lpSecurityAttrs用來說明保密屬性,一般為0。函數(shù)返回新建的 CWinThread對象的指針。

  程序應(yīng)該把AfxBeginThread 返回的CWinThread指針保存起來,以便對創(chuàng)建的線程進行控制。例如,可以調(diào)用CWinThread::SetThreadPriority來設(shè)置線程的優(yōu)先級,用CWinThread::SuspendThread來掛起線程。如果線程被掛起,那么直到調(diào)用CWinThread:: ResumeThread后線程才開始運行。

  如果要創(chuàng)建用戶界面線程,那么必須從CWinThread派生一個新類。事實上,代表進程主線程的CWinApp類就是CWinThread的派生類。派生類必須用 DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏來聲明和實現(xiàn)。需要重寫派生類的InitInstance、 ExitInstance、Run等函數(shù)。

  可以使用AfxBeginThread函數(shù)的另一個版本來創(chuàng)建用戶界面線程。函數(shù)的聲明為:

CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );

  參數(shù)pThreadClass指向一個CRuntimeClass對象,該對象是用RUNTIME_CLASS宏從CWinThread的派生類創(chuàng)建的。其它參數(shù)以及函數(shù)的返回值與第一個版本的AfxBeginThread是一樣的。
   當(dāng)發(fā)生下列事件之一時,線程被終止:
線程調(diào)用ExitThread。
線程函數(shù)返回,即線程隱含調(diào)用了ExitThread。
ExitProcess被進程的任一線程顯示或隱含調(diào)用。
用線程的句柄調(diào)用TerminateThread。
用進程句柄調(diào)用TerminateProcess。

2 線程的同步

多線程的使用會產(chǎn)生一些新的問題,主要是如何保證線程的同步執(zhí)行。多線程應(yīng)用程序需要使用同步對象和等待函數(shù)來實現(xiàn)同步。

12.2.1 為什么需要同步

由于同一進程的所有線程共享進程的虛擬地址空間,并且線程的中斷是匯編語言級的,所以可能會發(fā)生兩個線程同時訪問同一個對象(包括全局變量、共享資源、 API函數(shù)和MFC對象等)的情況,這有可能導(dǎo)致程序錯誤。例如,如果一個線程在未完成對某一大尺寸全局變量的讀操作時,另一個線程又對該變量進行了寫操作,那么第一個線程讀入的變量值可能是一種修改過程中的不穩(wěn)定值。

  屬于不同進程的線程在同時訪問同一內(nèi)存區(qū)域或共享資源時,也會存在同樣的問題。

  因此,在多線程應(yīng)用程序中,常常需要采取一些措施來同步線程的執(zhí)行。需要同步的情況包括以下幾種:

在多個線程同時訪問同一對象時,可能產(chǎn)生錯誤。例如,如果當(dāng)一個線程正在讀取一個至關(guān)重要的共享緩沖區(qū)時,另一個線程向該緩沖區(qū)寫入數(shù)據(jù),那么程序的運行結(jié)果就可能出錯。程序應(yīng)該盡量避免多個線程同時訪問同一個緩沖區(qū)或系統(tǒng)資源。

在Windows 95環(huán)境下編寫多線程應(yīng)用程序還需要考慮重入問題。Windows NT是真正的32位操作系統(tǒng),它解決了系統(tǒng)重入問題。而Windows 95由于繼承了Windows 3.x的部分16位代碼,沒能夠解決重入問題。這意味著在Windows 95中兩個線程不能同時執(zhí)行某個系統(tǒng)功能,否則有可能造成程序錯誤,甚至?xí)斐上到y(tǒng)崩潰。應(yīng)用程序應(yīng)該盡量避免發(fā)生兩個以上的線程同時調(diào)用同一個 Windows API函數(shù)的情況。

由于大小和性能方面的原因,MFC對象在對象級不是線程安全的,只有在類級才是。也就是說,兩個線程可以安全地使用兩個不同的CString對象,但同時使用同一個CString對象就可能產(chǎn)生問題。如果必須使用同一個對象,那么應(yīng)該采取適當(dāng)?shù)耐酱胧?/p>

多個線程之間需要協(xié)調(diào)運行。例如,如果第二個線程需要等待第一個線程完成到某一步時才能運行,那么該線程應(yīng)該暫時掛起以減少對CPU的占用時間,提高程序的執(zhí)行效率。當(dāng)?shù)谝粋€線程完成了相應(yīng)的步驟后,應(yīng)該發(fā)出某種信號來激活第二個線程。

 

12.2.2 等待函數(shù)

Win32 API提供了一組能使線程阻塞其自身執(zhí)行的等待函數(shù)。這些函數(shù)只有在作為其參數(shù)的一個或多個同步對象(見下小節(jié))產(chǎn)生信號時才會返回。在超過規(guī)定的等待時間后,不管有無信號,函數(shù)也都會返回。在等待函數(shù)未返回時,線程處于等待狀態(tài),此時線程只消耗很少的CPU時間。

  使用等待函數(shù)即可以保證線程的同步,又可以提高程序的運行效率。最常用的等待函數(shù)是WaitForSingleObject,該函數(shù)的聲明為:

DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);

  參數(shù)hHandle是同步對象的句柄。參數(shù)dwMilliseconds是以毫秒為單位的超時間隔,如果該參數(shù)為0,那么函數(shù)就測試同步對象的狀態(tài)并立即返回,如果該參數(shù)為INFINITE,則超時間隔是無限的。函數(shù)的返回值在表12.1中列出。

 

表12.1 WaitForSingleObject的返回值

返回值

含義

WAIT_FAILED

函數(shù)失敗

WAIT_OBJECT_0

指定的同步對象處于有信號的狀態(tài)

WAIT_ABANDONED

擁有一個mutex的線程已經(jīng)中斷了,但未釋放該MUTEX

WAIT_TIMEOUT

超時返回,并且同步對象無信號

 

 

函數(shù)WaitForMultipleObjects可以同時監(jiān)測多個同步對象,該函數(shù)的聲明為:

DWORD WaitForMultipleObjects(DWORD nCount, CONST HANDLE *lpHandles, BOOL bWaitAll, DWORD dwMilliseconds );

 

參數(shù)nCount是句柄數(shù)組中句柄的數(shù)目。lpHandles代表一個句柄數(shù)組。bWaitAll說明了等待類型,如果為TRUE,那么函數(shù)在所有對象都有信號后才返回,如果為FALSE,則只要有一個對象變成有信號的,函數(shù)就返回。函數(shù)的返回值在表12.2中列出。參數(shù)dwMilliseconds是以毫秒為單位的超時間隔,如果該參數(shù)為0,那么函數(shù)就測試同步對象的狀態(tài)并立即返回,如果該參數(shù)為INFINITE,則超時間隔是無限的。

 

表12.2 WaitForMultipleObjects的返回值

返回值

說明

WAIT_OBJECT_0到WAIT_ OBJECT_0+nCount-1

若bWaitAll為TRUE,則返回值表明所有對象都是有信號的。如果bWaitAll為FALSE,則返回值減去WAIT_OBJECT_0就是數(shù)組中有信號對象的最小索引。

WAIT_ABANDONED_0到WAIT_ ABANDONED_ 0+nCount-1

若bWaitAll為TRUE,則返回值表明所有對象都有信號,但有一個mutex被放棄了。若bWaitAll為FALSE,則返回值減去WAIT_ABANDONED_0就是被放棄mutex在對象數(shù)組中的索引。

WAIT_TIMEOUT

超時返回。

 

12.2.3 同步對象

  同步對象用來協(xié)調(diào)多線程的執(zhí)行,它可以被多個線程共享。線程的等待函數(shù)用同步對象的句柄作為參數(shù),同步對象應(yīng)該是所有要使用的線程都能訪問到的。同步對象的狀態(tài)要么是有信號的,要么是無信號的。同步對象主要有三種:事件、mutex和信號燈。

  事件對象(Event)是最簡單的同步對象,它包括有信號和無信號兩種狀態(tài)。在線程訪問某一資源之前,也許需要等待某一事件的發(fā)生,這時用事件對象最合適。例如,只有在通信端口緩沖區(qū)收到數(shù)據(jù)后,監(jiān)視線程才被激活。

事件對象是用CreateEvent函數(shù)建立的。該函數(shù)可以指定事件對象的種類和事件的初始狀態(tài)。如果是手工重置事件,那么它總是保持有信號狀態(tài),直到用ResetEvent函數(shù)重置成無信號的事件。如果是自動重置事件,那么它的狀態(tài)在單個等待線程釋放后會自動變?yōu)闊o信號的。用SetEvent可以把事件對象設(shè)置成有信號狀態(tài)。在建立事件時,可以為對象起個名字,這樣其它進程中的線程可以用OpenEvent函數(shù)打開指定名字的事件對象句柄。

  mutex對象的狀態(tài)在它不被任何線程擁有時是有信號的,而當(dāng)它被擁有時則是無信號的。mutex對象很適合用來協(xié)調(diào)多個線程對共享資源的互斥訪問(mutually exclusive)。

線程用CreateMutex函數(shù)來建立mutex對象,在建立mutex時,可以為對象起個名字,這樣其它進程中的線程可以用OpenMutex函數(shù)打開指定名字的mutex對象句柄。在完成對共享資源的訪問后,線程可以調(diào)用ReleaseMutex來釋放mutex,以便讓別的線程能訪問共享資源。如果線程終止而不釋放mutex,則認(rèn)為該mutex被廢棄。

  信號燈對象維護一個從0開始的計數(shù),在計數(shù)值大于0時對象是有信號的,而在計數(shù)值為0時則是無信號的。信號燈對象可用來限制對共享資源進行訪問的線程數(shù)量。線程用 CreateSemaphore函數(shù)來建立信號燈對象,在調(diào)用該函數(shù)時,可以指定對象的初始計數(shù)和最大計數(shù)。在建立信號燈時也可以為對象起個名字,別的進程中的線程可以用OpenSemaphore函數(shù)打開指定名字的信號燈句柄。

  一般把信號燈的初始計數(shù)設(shè)置成最大值。每次當(dāng)信號燈有信號使等待函數(shù)返回時,信號燈計數(shù)就會減1,而調(diào)用ReleaseSemaphore可以增加信號燈的計數(shù)。計數(shù)值越小就表明訪問共享資源的程序越多。

  除了上述三種同步對象外,表12.3中的對象也可用于同步。另外,有時可以用文件或通信設(shè)備作為同步對象使用。

 

表12.3 可用于同步的對象

對象

描述

變化通知

由FindFirstChangeNotification函數(shù)建立,當(dāng)在指定目錄中發(fā)生指定類型的變化時對象變成有信號的。

控制臺輸入

在控制臺建立是被創(chuàng)建。它是用CONIN$調(diào)用CreateFile函數(shù)返回的句柄,或是GetStdHandle函數(shù)的返回句柄。如果控制臺輸入緩沖區(qū)中有數(shù)據(jù),那么對象是有信號的,如果緩沖區(qū)為空,則對象是無信號的。

進程

當(dāng)調(diào)用CreateProcess建立進程時被創(chuàng)建。進程在運行時對象是無信號的,當(dāng)進程終止時對象是有信號的。

線程

當(dāng)調(diào)用Createprocess、CreateThread或CreateRemoteThread函數(shù)創(chuàng)建新線程時被創(chuàng)建。在線程運行是對象是無信號的,在線程終止時則是有信號的。

 

 

  當(dāng)對象不再使用時,應(yīng)該用CloseHandle函數(shù)關(guān)閉對象句柄。

清單12.3是一個使用事件對象的簡單例子,在該例中,假設(shè)主線程要讀取共享緩沖區(qū)中的內(nèi)容,而輔助線程負責(zé)向緩沖區(qū)中寫入數(shù)據(jù)。兩個線程使用了一個 hEvent事件對象來同步。在用CreateEvent函數(shù)創(chuàng)建事件對象句柄時,指定該對象是一個自動重置事件,其初始狀態(tài)為有信號的。當(dāng)線程要讀寫緩沖區(qū)時,調(diào)用WaitForSingleObject函數(shù)無限等待hEvent信號。如果hEvent無信號,則說明另一線程正在訪問緩沖區(qū);如果有信號,則本線程可以訪問緩沖區(qū),WaitForSingleObject函數(shù)在返回后會自動把hEvent置成無信號的,這樣在本線程讀寫緩沖區(qū)時別的線程不會同時訪問。在完成讀寫操作后,調(diào)用SetEvent函數(shù)把hEvent置成有信號的,以使別的線程有機會訪問共享緩沖區(qū)。

 

清單12.3 使用事件對象的簡單例子

HANDLE hEvent; 
//全局變量
//主線程
hEvent=CreateEvent(NULL, FALSE, TRUE, NULL);
if(hEvent= =NULL) return;
WaitForSingleObject(hEvent, INFINITE);
ReadFromBuf( );
SetEvent( hEvent );
CloseHandle( hEvent );

//輔助線程
UINT MyThreadProc( LPVOID pParam )

{
    . . .
    WaitForSingleObject(hEvent, INFINITE);
    WriteToBuf( );
    SetEvent( hEvent );
    . . .
        
    
return 0// 線程正常結(jié)束
}

12.2.4 關(guān)鍵節(jié)和互鎖變量訪問

  關(guān)鍵節(jié)(Critical Seciton)與mutex的功能類似,但它只能由同一進程中的線程使用。關(guān)鍵節(jié)可以防止共享資源被同時訪問。

進程負責(zé)為關(guān)鍵節(jié)分配內(nèi)存空間,關(guān)鍵節(jié)實際上是一個CRITICAL_SECTION型的變量,它一次只能被一個線程擁有。在線程使用關(guān)鍵節(jié)之前,必須調(diào)用InitializeCriticalSection函數(shù)將其初始化。如果線程中有一段關(guān)鍵的代碼不希望被別的線程中斷,那么可以調(diào)用 EnterCriticalSection函數(shù)來申請關(guān)鍵節(jié)的所有權(quán),在運行完關(guān)鍵代碼后再用LeaveCriticalSection函數(shù)來釋放所有權(quán)。如果在調(diào)用EnterCriticalSection時關(guān)鍵節(jié)對象已被另一個線程擁有,那么該函數(shù)將無限期等待所有權(quán)。

利用互鎖變量可以建立簡單有效的同步機制。使用函數(shù)InterlockedIncrement和InterlockedDecrement可以增加或減少多個線程共享的一個32位變量的值,并且可以檢查結(jié)果是否為0。線程不必擔(dān)心會被其它線程中斷而導(dǎo)致錯誤。如果變量位于共享內(nèi)存中,那么不同進程中的線程也可以使用這種機制。

3 串行通信與重疊I/O

Win 32系統(tǒng)為串行通信提供了全新的服務(wù)。傳統(tǒng)的OpenComm、ReadComm、WriteComm、CloseComm等函數(shù)已經(jīng)過時,WM_COMMNOTIFY消息也消失了。取而代之的是文件I/O函數(shù)提供的打開和關(guān)閉通信資源句柄及讀寫操作的基本接口。

  新的文件I/O函數(shù)(CreateFile、ReadFile、WriteFile等)支持重疊式輸入輸出,這使得線程可以從費時的I/O操作中解放出來,從而極大地提高了程序的運行效率。

12.3.1 串行口的打開和關(guān)閉

  Win 32系統(tǒng)把文件的概念進行了擴展。無論是文件、通信設(shè)備、命名管道、郵件槽、磁盤、還是控制臺,都是用API函數(shù)CreateFile來打開或創(chuàng)建的。該函數(shù)的聲明為:

HANDLE CreateFile(

LPCTSTR lpFileName, // 文件名

DWORD dwDesiredAccess, // 訪問模式

DWORD dwShareMode, // 共享模式

LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 通常為NULL

DWORD dwCreationDistribution, // 創(chuàng)建方式

DWORD dwFlagsAndAttributes, // 文件屬性和標(biāo)志

HANDLE hTemplateFile // 臨時文件的句柄,通常為NULL

);

  如果調(diào)用成功,那么該函數(shù)返回文件的句柄,如果調(diào)用失敗,則函數(shù)返回INVALID_HANDLE_VALUE。

如果想要用重疊I/O方式(參見12.3.3)打開COM2口,則一般應(yīng)象清單12.4那樣調(diào)用CreateFile函數(shù)。注意在打開一個通信端口時,應(yīng)該以獨占方式打開,另外要指定GENERIC_READ、GENERIC_WRITE、OPEN_EXISTING和 FILE_ATTRIBUTE_NORMAL等屬性。如果要打開重疊I/O,則應(yīng)該指定 FILE_FLAG_OVERLAPPED屬性。

 

清單12.4

HANDLE hCom;
DWORD dwError;
hCom
=CreateFile(“COM2”,  //文件名
                GENERIC_READ | GENERIC_WRITE, // 允許讀和寫    
                0// 獨占方式
                NULL,
                OPEN_EXISTING, 
//打開而不是創(chuàng)建
                FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // 重疊方式
                NULL
                );

if(hCom = = INVALID_HANDLE_VALUE)
{
    dwError
=GetLastError( );    
    . . . 
// 處理錯誤
}

當(dāng)不再使用文件句柄時,應(yīng)該調(diào)用CloseHandle函數(shù)關(guān)閉之。

12.3.2 串行口的初始化

  在打開通信設(shè)備句柄后,常常需要對串行口進行一些初始化工作。這需要通過一個DCB結(jié)構(gòu)來進行。DCB結(jié)構(gòu)包含了諸如波特率、每個字符的數(shù)據(jù)位數(shù)、奇偶校驗和停止位數(shù)等信息。在查詢或配置置串行口的屬性時,都要用DCB結(jié)構(gòu)來作為緩沖區(qū)。

調(diào)用GetCommState函數(shù)可以獲得串口的配置,該函數(shù)把當(dāng)前配置填充到一個DCB結(jié)構(gòu)中。一般在用CreateFile打開串行口后,可以調(diào)用 GetCommState函數(shù)來獲取串行口的初始配置。要修改串行口的配置,應(yīng)該先修改DCB結(jié)構(gòu),然后再調(diào)用SetCommState函數(shù)用指定的 DCB結(jié)構(gòu)來設(shè)置串行口。

  除了在DCB中的設(shè)置外,程序一般還需要設(shè)置I/O緩沖區(qū)的大小和超時。Windows用I/O緩沖區(qū)來暫存串行口輸入和輸出的數(shù)據(jù),如果通信的速率較高,則應(yīng)該設(shè)置較大的緩沖區(qū)。調(diào)用SetupComm函數(shù)可以設(shè)置串行口的輸入和輸出緩沖區(qū)的大小。

在用ReadFile和WriteFile讀寫串行口時,需要考慮超時問題。如果在指定的時間內(nèi)沒有讀出或?qū)懭胫付〝?shù)量的字符,那么ReadFile或 WriteFile的操作就會結(jié)束。要查詢當(dāng)前的超時設(shè)置應(yīng)調(diào)用GetCommTimeouts函數(shù),該函數(shù)會填充一個COMMTIMEOUTS結(jié)構(gòu)。調(diào)用SetCommTimeouts可以用某一個COMMTIMEOUTS結(jié)構(gòu)的內(nèi)容來設(shè)置超時。

  有兩種超時:間隔超時和總超時。間隔超時是指在接收時兩個字符之間的最大時延,總超時是指讀寫操作總共花費的最大時間。寫操作只支持總超時,而讀操作兩種超時均支持。用COMMTIMEOUTS結(jié)構(gòu)可以規(guī)定讀/寫操作的超時,該結(jié)構(gòu)的定義為:

typedef struct _COMMTIMEOUTS {

DWORD ReadIntervalTimeout; // 讀間隔超時

DWORD ReadTotalTimeoutMultiplier; // 讀時間系數(shù)

DWORD ReadTotalTimeoutConstant; // 讀時間常量

DWORD WriteTotalTimeoutMultiplier; // 寫時間系數(shù)

DWORD WriteTotalTimeoutConstant; // 寫時間常量

} COMMTIMEOUTS,*LPCOMMTIMEOUTS;

  COMMTIMEOUTS結(jié)構(gòu)的成員都以毫秒為單位。總超時的計算公式是:

總超時=時間系數(shù)×要求讀/寫的字符數(shù) + 時間常量

  例如,如果要讀入10個字符,那么讀操作的總超時的計算公式為:

讀總超時=ReadTotalTimeoutMultiplier×10 + ReadTotalTimeoutConstant

  可以看出,間隔超時和總超時的設(shè)置是不相關(guān)的,這可以方便通信程序靈活地設(shè)置各種超時。

如果所有寫超時參數(shù)均為0,那么就不使用寫超時。如果ReadIntervalTimeout為0,那么就不使用讀間隔超時,如果 ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant都為0,則不使用讀總超時。如果讀間隔超時被設(shè)置成MAXDWORD并且兩個讀總超時為0,那么在讀一次輸入緩沖區(qū)中的內(nèi)容后讀操作就立即完成,而不管是否讀入了要求的字符。

  在用重疊方式讀寫串行口時,雖然ReadFile和WriteFile在完成操作以前就可能返回,但超時仍然是起作用的。在這種情況下,超時規(guī)定的是操作的完成時間,而不是ReadFile和WriteFile的返回時間。

清單12.5列出了一段簡單的串行口初始化代碼。

 

清單12.5 打開并初始化串行口

HANDLE hCom;
DWORD dwError;
DCB dcb;
COMMTIMEOUTS TimeOuts;

hCom
=CreateFile(“COM2”, // 文件名        
                GENERIC_READ | GENERIC_WRITE, // 允許讀和寫
                0// 獨占方式
                NULL,
                OPEN_EXISTING, 
//打開而不是創(chuàng)建
                FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // 重疊方式
                NULL
                );

if(hCom = = INVALID_HANDLE_VALUE)
{
    dwError
=GetLastError( );    
    . . . 
// 處理錯誤
}

SetupComm( hCom, 
10241024 ) //緩沖區(qū)的大小為1024
TimeOuts. ReadIntervalTimeout=1000;
TimeOuts.ReadTotalTimeoutMultiplier
=500;
TimeOuts.ReadTotalTimeoutConstant
=5000;
TimeOuts.WriteTotalTimeoutMultiplier
=500;
TimeOuts.WriteTotalTimeoutConstant
=5000;
SetCommTimeouts(hCom, 
&TimeOuts); // 設(shè)置超時
GetCommState(hCom, &dcb);
dcb.BaudRate
=2400// 波特率為2400
dcb.ByteSize=8// 每個字符有8位
dcb.Parity=NOPARITY; //無校驗
dcb.StopBits=ONESTOPBIT; //一個停止位
SetCommState(hCom, &dcb);

 

12.3.3 重疊I/O

在用ReadFile和WriteFile讀寫串行口時,既可以同步執(zhí)行,也可以重疊(異步)執(zhí)行。在同步執(zhí)行時,函數(shù)直到操作完成后才返回。這意味著在同步執(zhí)行時線程會被阻塞,從而導(dǎo)致效率下降。在重疊執(zhí)行時,即使操作還未完成,調(diào)用的函數(shù)也會立即返回。費時的I/O操作在后臺進行,這樣線程就可以干別的事情。例如,線程可以在不同的句柄上同時執(zhí)行I/O操作,甚至可以在同一句柄上同時進行讀寫操作。“重疊”一詞的含義就在于此。

  ReadFile函數(shù)只要在串行口輸入緩沖區(qū)中讀入指定數(shù)量的字符,就算完成操作。而WriteFile函數(shù)不但要把指定數(shù)量的字符拷入到輸出緩沖中,而且要等這些字符從串行口送出去后才算完成操作。

ReadFile和WriteFile函數(shù)是否為執(zhí)行重疊操作是由CreateFile函數(shù)決定的。如果在調(diào)用CreateFile創(chuàng)建句柄時指定了 FILE_FLAG_OVERLAPPED標(biāo)志,那么調(diào)用ReadFile和WriteFile對該句柄進行的讀寫操作就是重疊的,如果未指定重疊標(biāo)志,則讀寫操作是同步的。

  函數(shù)ReadFile和WriteFile的參數(shù)和返回值很相似。這里僅列出ReadFile函數(shù)的聲明:

BOOL ReadFile(

HANDLE hFile, // 文件句柄

LPVOID lpBuffer, // 讀緩沖區(qū)

DWORD nNumberOfBytesToRead, // 要求讀入的字節(jié)數(shù)

LPDWORD lpNumberOfBytesRead, // 實際讀入的字節(jié)數(shù)

LPOVERLAPPED lpOverlapped // 指向一個OVERLAPPED結(jié)構(gòu)

); //若返回TRUE則表明操作成功

 

需要注意的是如果該函數(shù)因為超時而返回,那么返回值是TRUE。參數(shù)lpOverlapped在重疊操作時應(yīng)該指向一個OVERLAPPED結(jié)構(gòu),如果該參數(shù)為NULL,那么函數(shù)將進行同步操作,而不管句柄是否是由FILE_FLAG_OVERLAPPED標(biāo)志建立的。

當(dāng)ReadFile和WriteFile返回FALSE時,不一定就是操作失敗,線程應(yīng)該調(diào)用GetLastError函數(shù)分析返回的結(jié)果。例如,在重疊操作時如果操作還未完成函數(shù)就返回,那么函數(shù)就返回FALSE,而且GetLastError函數(shù)返回ERROR_IO_PENDING。

在使用重疊I/O時,線程需要創(chuàng)建OVERLAPPED結(jié)構(gòu)以供讀寫函數(shù)使用。OVERLAPPED結(jié)構(gòu)最重要的成員是hEvent,hEvent是一個事件對象句柄,線程應(yīng)該用CreateEvent函數(shù)為hEvent成員創(chuàng)建一個手工重置事件,hEvent成員將作為線程的同步對象使用。如果讀寫函數(shù)未完成操作就返回,就那么把hEvent成員設(shè)置成無信號的。操作完成后(包括超時),hEvent會變成有信號的。

如果GetLastError函數(shù)返回ERROR_IO_PENDING,則說明重疊操作還為完成,線程可以等待操作完成。有兩種等待辦法:一種辦法是用象WaitForSingleObject這樣的等待函數(shù)來等待OVERLAPPED結(jié)構(gòu)的hEvent成員,可以規(guī)定等待的時間,在等待函數(shù)返回后,調(diào)用GetOverlappedResult。另一種辦法是調(diào)用GetOverlappedResult函數(shù)等待,如果指定該函數(shù)的bWait參數(shù)為 TRUE,那么該函數(shù)將等待OVERLAPPED結(jié)構(gòu)的hEvent 事件。GetOverlappedResult可以返回一個OVERLAPPED結(jié)構(gòu)來報告包括實際傳輸字節(jié)在內(nèi)的重疊操作結(jié)果。

如果規(guī)定了讀/寫操作的超時,那么當(dāng)超過規(guī)定時間后,hEvent成員會變成有信號的。因此,在超時發(fā)生后,WaitForSingleObject和 GetOverlappedResult都會結(jié)束等待。WaitForSingleObject的dwMilliseconds參數(shù)會規(guī)定一個等待超時,該函數(shù)實際等待的時間是兩個超時的最小值。注意GetOverlappedResult不能設(shè)置等待的時限,因此如果hEvent成員無信號,則該函數(shù)將一直等待下去。

  在調(diào)用ReadFile和WriteFile之前,線程應(yīng)該調(diào)用ClearCommError函數(shù)清除錯誤標(biāo)志。該函數(shù)負責(zé)報告指定的錯誤和設(shè)備的當(dāng)前狀態(tài)。

  調(diào)用PurgeComm函數(shù)可以終止正在進行的讀寫操作,該函數(shù)還會清除輸入或輸出緩沖區(qū)中的內(nèi)容。

 

12.3.4 通信事件

在Windows 95/NT中,WM_COMMNOTIFY消息已經(jīng)取消,在串行口產(chǎn)生一個通信事件時,程序并不會收到通知消息。線程需要調(diào)用WaitCommEvent 函數(shù)來監(jiān)視發(fā)生在串行口中的各種事件,該函數(shù)的第二個參數(shù)返回一個事件屏蔽變量,用來指示事件的類型。線程可以用SetCommMask建立事件屏蔽以指定要監(jiān)視的事件,表12.4列出了可以監(jiān)視的事件。調(diào)用GetCommMask可以查詢串行口當(dāng)前的事件屏蔽。

 

表12.4 通信事件

事件屏蔽

含義

EV_BREAK

檢測到一個輸入中斷

EV_CTS

CTS信號發(fā)生變化

EV_DSR

DSR信號發(fā)生變化

EV_ERR

發(fā)生行狀態(tài)錯誤

EV_RING

檢測到振鈴信號

EV_RLSD

RLSD(CD)信號發(fā)生變化

EV_RXCHAR

輸入緩沖區(qū)接收到新字符

EV_RXFLAG

輸入緩沖區(qū)收到事件字符

EV_TXEMPTY

發(fā)送緩沖區(qū)為空

WaitCommEvent即可以同步使用,也可以重疊使用。如果串口是用FILE_FLAG_OVERLAPPED標(biāo)志打開的,那么 WaitCommEvent就進行重疊操作,此時該函數(shù)需要一個OVERLAPPED結(jié)構(gòu)。線程可以調(diào)用等待函數(shù)或 GetOverlappedResult函數(shù)來等待重疊操作的完成。

  當(dāng)指定范圍內(nèi)的某一事件發(fā)生后,線程就結(jié)束等待并把該事件的屏蔽碼設(shè)置到事件屏蔽變量中。需要注意的是,WaitCommEvent只檢測調(diào)用該函數(shù)后發(fā)生的事件。例如,如果在調(diào)用WaitCommEvent前在輸入緩沖區(qū)中就有字符,則不會因為這些字符而產(chǎn)生EV_RXCHAR事件。

  如果檢測到輸入的硬件信號(如CTS、RTS和CD信號等)發(fā)生了變化,線程可以調(diào)用GetCommMaskStatus函數(shù)來查詢它們的狀態(tài)。而用EscapeCommFunction函數(shù)可以控制輸出的硬件信號(如DTR和RTS信號)。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    日韩一区二区三区在线日| 欧美做爰猛烈叫床大尺度| 精品日韩av一区二区三区| 国产伦精品一区二区三区高清版| 国产丝袜极品黑色高跟鞋| 玩弄人妻少妇一区二区桃花| 国产不卡免费高清视频| 婷婷基地五月激情五月| 亚洲视频一区二区久久久| 人妻一区二区三区多毛女| 国产美女精品人人做人人爽| 午夜精品一区二区三区国产| 天堂热东京热男人天堂| 成在线人免费视频一区二区| 国产精品一区二区成人在线| 少妇在线一区二区三区| 一个人的久久精彩视频| 1024你懂的在线视频| 麻豆精品视频一二三区| 亚洲一区二区三区在线免费| 亚洲高清欧美中文字幕| 欧美日韩在线观看自拍| 亚洲欧美日韩网友自拍| 在线免费看国产精品黄片| 日韩精品一级一区二区| 欧美丰满大屁股一区二区三区| 国产又粗又硬又长又爽的剧情| 九九蜜桃视频香蕉视频| 国产一区欧美一区二区| 国产精品欧美一级免费| 精品一区二区三区三级视频| 免费性欧美重口味黄色| 国产精品国三级国产专不卡| 中文字幕一区二区免费| 免费大片黄在线观看日本| 少妇毛片一区二区三区| 色婷婷国产精品视频一区二区保健| 中文字幕人妻日本一区二区| 亚洲中文字幕综合网在线| 精品女同在线一区二区| 久久经典一区二区三区|