1. 程式人生 > >事件驅動機制和訊息迴圈

事件驅動機制和訊息迴圈

http://www.qudong.com/soft/program/C/rumenjiaocheng/20080317/1362.html      

Windows系統是一個訊息驅動的OS,什麼是訊息呢?我很難說得清楚,也很難下一個定義(誰在噓我),我下面從不同的幾個方面講解一下,希望大家看了後有一點了解。

1、訊息的組成:一個訊息由一個訊息名稱(UINT),和兩個引數(WPARAM,LPARAM)。當用戶進行了輸入或是視窗的狀態發生改變時系統都會發送訊息到某一個視窗。例如當選單轉中之後會有WM_COMMAND訊息傳送,WPARAM的高字中(HIWORD(wParam))是命令的ID號,對選單來講就是選單ID。當然使用者也可以定義自己的訊息名稱,也可以利用自定義訊息來發送通知和傳送資料。

2、誰將收到訊息:一個訊息必須由一個視窗接收。在視窗的過程(WNDPROC)中可以對訊息進行分析,對自己感興趣的訊息進行處理。例如你希望對選單選擇進行處理那麼你可以定義對WM_COMMAND進行處理的程式碼,如果希望在視窗中進行圖形輸出就必須對WM_PAINT進行處理。

3、未處理的訊息到那裡去了:M$為視窗編寫了預設的視窗過程,這個視窗過程將負責處理那些你不處理訊息。正因為有了這個預設視窗過程我們才可以利用Windows的視窗進行開發而不必過多關注視窗各種訊息的處理。例如視窗在被拖動時會有很多訊息傳送,而我們都可以不予理睬讓系統自己去處理。

4、視窗控制代碼:說到訊息就不能不說視窗控制代碼,

系統通過視窗控制代碼來在整個系統中唯一標識一個視窗,傳送一個訊息時必須指定一個視窗控制代碼表明該訊息由那個視窗接收。而每個視窗都會有自己的視窗過程,所以使用者的輸入就會被正確的處理。例如有兩個視窗共用一個視窗過程程式碼,你在視窗一上按下滑鼠時訊息就會通過視窗一的控制代碼被髮送到視窗一而不是視窗二。

5、示例:下面有一段虛擬碼演示如何在視窗過程中處理訊息

LONG yourWndProc(HWND hWnd,UINT uMessageType,WPARAM wP,LPARAM)
{
	switch(uMessageType)
	{//使用SWITCH語句將各種訊息分開
		case(WM_PAINT):
			doYourWindow(...);//在視窗需要重新繪製時進行輸出
		break;
		case(WM_LBUTTONDOWN):
			doYourWork(...);//在滑鼠左鍵被按下時進行處理
		break;
		default:
			callDefaultWndProc(...);//對於其它情況就讓
系統
自己處理 break; } }

接下來談談什麼是訊息機制:系統將會維護一個或多個訊息佇列,所有產生的訊息都回被放入或是插入佇列中。系統會在佇列中取出每一條訊息,根據訊息的接收控制代碼而將該訊息傳送給擁有該視窗的程式的訊息迴圈。每一個執行的程式都有自己的訊息迴圈,在迴圈中得到屬於自己的訊息並根據接收視窗的控制代碼呼叫相應的視窗過程。而在沒有訊息時訊息迴圈就將控制權交給系統所以Windows可以同時進行多個任務。下面的虛擬碼演示了訊息迴圈的用法:

while(1)
{
	id=getMessage(...);
	if(id == quit)
		break;
	translateMessage(...);
}

當該程式沒有訊息通知時getMessage就不會返回,也就不會佔用系統的CPU時間。 下圖為訊息投遞模式

在16位的系統系統中只有一個訊息佇列,所以系統必須等待當前任務處理訊息後才可以傳送下一訊息到相應程式,如果一個程式陷如死迴圈或是耗時操作時系統就會得不到控制權。這種多工系統也就稱為協同式的多工系統。Windows3.X就是這種系統

而32位的系統中每一執行的程式都會有一個訊息佇列,所以系統可以在多個訊息佇列中轉換而不必等待當前程式完成訊息處理就可以得到控制權。這種多工系統就稱為搶先式的多工系統。Windows95/NT就是這種系統

1、Windows事件驅動機制

  我們當中不少使用VC、Delphi等作為開發語言的程式設計師是一步步從DOS下的Basic、C++中走過來的,而且大多在剛開始學習程式設計時也是先從DOS下的程式設計環境入手的,因此在習慣了DOS下的過程驅動形式的順序程式設計方法後,往往在向Windows下的開發環境轉型的過程中會對Windows所採取的事件驅動方式感到無法適應。因為DOS和Windows這兩種作業系統的執行機制是截然不同的,DOS下的任何程式都是使用順序的、過程驅動的程式設計方法。這種程式都有一個明顯的開始、明顯的過程以及一個明顯的結束,因此通過程式就能直接控制程式事件或過程的全部順序。即使是在處理異常時,處理過程也仍然是順序的、過程驅動的結構。而Windows的驅動方式則是事件驅動的,即程式的流程不是由事件的順序來控制,而是由事件的發生來控制,所有的事件是無序的,所為一個程式設計師,在編寫程式時,並不知道使用者會先按下哪個按紐,也就不知道程式先觸發哪個訊息。因此我們的主要任務就是對正在開發的應用程式要發出的或要接收的訊息進行排序和管理。事件驅動程式設計是密切圍繞訊息的產生與處理而展開的,一條訊息是關於發生的事件的訊息。


2、Windows的訊息迴圈
  Windows作業系統為每一個正在執行的應用程式保持有一個訊息佇列。當有事件發生後,Windows並不是將這個激發事件直接送給應用程式,而是先將其翻譯成一個Windows訊息,然後再把這個訊息加入到這個應用程式的訊息佇列中去。應用程式需要通過訊息迴圈來接收這些訊息。在MFC中使用了對WinAPI進行了很好封裝的類庫,雖然可以為程式設計提供一個面向物件的介面,使Windows程式設計師能夠以面象物件的方式進行程式設計,把那些進行SDK程式設計時最繁瑣的部分提供給程式設計師,使之專注於功能的實現,但是由於引入了很好的封裝特性,使我們不能直接操縱部分核心程式碼。對於訊息的迴圈和接收也只是通過類似於下面的訊息對映予以很簡單的表示:

  BEGIN_MESSAGE_MAP(CTEMMSView, CFormView)

  //{{AFX_MSG_MAP(CTEMMSView)

  ON_WM_LBUTTONDOWN()

  ON_COMMAND(ID_OPENDATA, OnOpenData)

  ON_WM_TIMER()

  ON_WM_PAINT()

  //}}AFX_MSG_MAP

  END_MESSAGE_MAP()

  雖然上述訊息對映在程式設計過程中處理訊息非常簡練方便,但顯然是難於理解訊息是如何參與迴圈和分發的。因此有必要通過SDK(Software Developers Kit,軟體開發工具箱)程式碼深入到被MFC封裝的Windows程式設計的核心中來研究其具體是如何工作的。在SDK程式設計中,一般是在Windows應用程式的入口點WinMain函式中新增處理訊息迴圈的程式碼以檢索Windows送來的訊息,然後WinMain再把這些訊息分配給相應的視窗函式並處理它們:

  ……

  MSG msg; //定義訊息名

  while (GetMessage (&msg, NULL, 0, 0))

  {

  TranslateMessage (&msg) ; //翻譯訊息

  DispatchMessage (&msg) ; //撤去訊息

  }

  return msg.wParam ;

  上述幾句雖然簡單但卻是所有Windows程式的關鍵程式碼,擔負著獲取、解釋和分發訊息的任務,下面就重點對其功能和作用進行分析:

  MSG結構在標頭檔案中定義如下:

  typedef struct tagMSG

  {

  HWND hwnd;

  UINT message;

  WPARAM wParam;

  LPARAM lParam;

  DWORD time;

  POINT pt;

  } MSG, *PMSG;

  其資料成員的具體意義如下:

  hwnd:訊息將要傳送到的那個視窗的控制代碼,用這個引數可以決定讓哪個視窗接收訊息。

  message:訊息號,它唯一標識了一種訊息型別。每種訊息型別都在Windows檔案進行了預定義。

  wParam:一個32位的訊息引數,這個值的確切意義取決於訊息本身。

  lParam:同上。

  time:訊息放入訊息佇列中的時間,在這個域中寫入的並非當時日期,而是從Windows啟動後所測量的時間值。Windows用

  這個域來使用訊息保持正確的順序。

  pt:訊息放入訊息佇列時的滑鼠座標。

  訊息迴圈以GetMessage呼叫開始,它從訊息佇列中取出一個訊息。該函式的四個引數可以有控制地獲取訊息,第一個引數指定要接收訊息的MSG結構的地址,第二個引數表示視窗控制代碼,一般將其設定為空,表示要獲取該應用程式建立的所有視窗的訊息;第三、四引數用於指定訊息範圍。後面三個引數被設定為預設值,用於接收發送到屬於這個應用程式的任何一個視窗的所有訊息。在接收到除WM_QUIT之外的任何一個訊息後,GetMessage()返回TRUE;如果GetMessage收到一個WM_QUIT訊息,則返回FALSE以退出訊息迴圈,終止程式執行。因此,在接收到WM_QUIT之前,帶有GetMessage()的訊息迴圈可以一直迴圈下去。當除WM_QUIT的訊息用GetMessage讀入後,首先要經過函式TranslateMessage()對其進行解釋,但對大多數訊息來說並不起什麼作用。這裡起關鍵作用的是DispatchMessage()函式,把由GetMessage獲取的Windows訊息傳送給在MSG結構中為視窗所指定的視窗過程。在訊息處理函式處理完訊息之後,程式碼又迴圈到開始去接收另一個訊息,這樣就完成了一個完整的訊息迴圈。

  由於Windows作業系統是一種非剝奪式多工作業系統。只有在應用程式主動交出CPU控制權後,Windows才能把控制權交給其他應用程式。在訊息迴圈中,一定要有能交出控制的系統函式才能實現協同式多工操作。能完成該功能的只有GetMessage、PeekMessage和WaitMessage這三個函式,如果在應用程式中長期不去呼叫這三個函式之一其他任務則無法執行。GetMessage函式在找不到等待應用程式處理的訊息時,會自動交出控制權,由Windows把CPU的控制權交給其他等待獲取控制權的應用程式。由於任何Windows應用程式都含有一個訊息迴圈,這種隱式交出控制權的方式可以保證合併各個應用程式共享控制權。一旦發往該應用程式的訊息到達應用程式佇列,即開始執行GetMessage語句的下一條語句。使用GetMessage函式的訊息迴圈在訊息佇列中沒有訊息時將等待,如果需要,可以利用這段時間進行I/O埠操作等耗時操作,不過需要在訊息迴圈中使用PeekMessage函式來代替GetMessage。使用PeekMessage的方法同GetMessage類似,下面是一段使用PeekMessage函式的訊息迴圈的典型例子:

  MSG msg;

  BOOL bDone=FALSE;

  do{

  if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)){

  if(msg.message==WM_QUIT)

  bDone=TRUE;

  else{

  TranslateMessage(&msg);

  DispatchMessage(&msg);

  }

  }

  //無訊息處理,進行長時間操作

  else{

  ……//長時間操作

  }

  }while(!bDone)

  ……

  無論應用程式訊息佇列中是否有訊息,PeekMessage函式都立即返回,如果希望等待新訊息入隊,可以利用無返回值的函式WaitMessage配合PeekMessage進行訊息迴圈。

  四、對Windowds訊息的處理

  視窗過程處理訊息通常以switch語句開始,對於它要處理的每一條訊息ID都跟有一條case語句,這在功能上同MFC的訊息對映有些類似:

  switch(uMsgId)

  {

  case WM_TIMER:

  //對WM_TIMER定時器訊息的處理過程

  return 0;

  case WM_LBUTTONDOWN:

  //對WM_ LBUTTONDOWN滑鼠左鍵單擊訊息的處理過程

  ruturn 0;

  ……

  default:

  //其他訊息由這個預設處理函式來處理

  return DefWindowProc(hwnd,uMsgId,wParam,lParam);

  }

  在處理完訊息後必須返回0,這很重要,否則Windows將要不停地重試下去。對於那些在程式中不準備處理的訊息,視窗過程會把它們都扔給DefWindowProc進行預設處理,而且還要返回那個函式的返回值。在訊息傳遞層次中,可以認為DefWindowProc函式是最頂層的函式。該函式發出WM_SYSCOMMAND訊息,由系統執行Windows環境中多數視窗所公用的各種通用操作,如更新視窗的正文標題等等。 在MFC下可以用下述部分程式碼實現與上述SDK程式碼相同的功能:

  BEGIN_MESSAGE_MAP(CTEMMSView, CFormView)

  //{{AFX_MSG_MAP(CTEMMSView)

  ON_WM_LBUTTONDOWN()

  ON_WM_TIMER()

  //}}AFX_MSG_MAP

  END_MESSAGE_MAP()

  小結:Windows環境提供有非常豐富的系統資源,在這個基礎上可以編制出能滿足各種各樣目標功能的應用系統。要深入Windows程式設計就必須首先對Windows系統的執行機理有很好的認識,本文僅針對Windows的一種重要執行機制--訊息機制作了較深入的剖析和闡述。對培養在Windows下的程式設計思想有一定的幫助。對某些相關問題的詳細論述可以參考MSDN線上幫助的"SDK Reference"部分。

http://blog.csdn.net/sshhbb/archive/2010/12/14/6076095.aspx

1.視窗(Windows)和控制代碼(HANDLE,handle:視窗控制代碼(HWND)圖示控制代碼(HICON)、游標控制代碼(HCURSOR)和畫刷控制代碼(HBRUSH
2.訊息,訊息佇列,訊息迴圈,訊息響應
 .OS將操作包裝成Message
 .typedef struct MSG {       
      HWND   hwnd; //視窗控制代碼,即標示訊息所屬的視窗     
      UINT   message;//標示訊息的類別,是滑鼠還是鍵盤等 如滑鼠左鍵按下訊息是WM_LBUTTONDOWN,鍵盤按下訊息是WM_KEYDOWN,字元訊息是WM_CHAR
      WPARAM wParam;//訊息的附加資訊
      LPARAM lParam;//訊息的附加資訊
      DWORD  time;//訊息投遞到訊息佇列中的時間
      POINT  pt;//滑鼠的當前位置
   } MSG;
 .訊息佇列每一個Windows應用程式開始執行後,系統都會為該程式建立一個訊息佇列,這個訊息佇列用來存放該程式建立的視窗的訊息
 .進隊訊息(OS將產生的訊息放在應用程式的訊息佇列中,讓應用程式來處理)
  不進隊訊息(OS直接呼叫視窗的處理過程)
 .Windows應用程式的訊息處理機制
  while(GetMessage(&msg,NULL,0,0)){//接收到WM_QUIT訊息時,才返回0
    
TranslateMessage(&msg);//對訊息進行包裝處理然後再以訊息的形式投放到訊息佇列
    
DispatchMessage(&msg);//訊息回傳給作業系統,由作業系統呼叫視窗過程函式對訊息進行處理
 
}

(1)作業系統接收到應用程式的視窗訊息,將訊息投遞到該應用程式的訊息佇列中。
2)應用程式在訊息迴圈中呼叫GetMessage函式從訊息佇列中取出一條條的訊息。取出後,以對訊息進行一些預處理,如放棄對某些訊息的響應,或者呼叫TranslateMessage產生新的訊息
3)應用程式呼叫DispatchMessage,將訊息回傳給作業系統。訊息是由MSG結構體物件來表示的,其中就包含了接收訊息的視窗的控制代碼。因此,DispatchMessage函式總能進行正確的傳遞。4)系統利用WNDCLASS結構體的lpfnWndProc成員儲存的視窗過程函式的指標呼叫視窗過程,對訊息進行處理(即“系統給應用程式傳送了訊息”)。
 .視窗過程函式
   lresult callback windowproc(

        hwnd hwnd,          // 對應訊息的視窗控制代碼

        uint umsg,           // 訊息程式碼,區別訊息的型別

        wparam wparam,      // 訊息程式碼的附加引數1

        lparam lparam       // 訊息程式碼的附加引數2

       );
【蔡學鏞.揭開訊息迴圈的神祕面紗】


簡單歸納如下:
   訊息迴圈被封裝進了 Application 類別的 Run() 靜態方法中;Windows Procedure 被封裝進了 NativeWindow 與 Control 類別中;
個別的訊息處理動作被封裝進 Control 類別的 OnXyz()(例如 OnPaint())。我們可以覆蓋(override)OnXyz(),來提供我們自己的程式。
也可以利用.NET的事件(event)機制,在 Xyz 事件上,加入我們的事件處理函式(Event Handler)。Control 類別的 OnXyz() 會主動呼叫 Xyz 事件的處理函式。
請注意,因為 Xyz 的事件處理函式是由 Control 類別的 OnXyz() 方法所呼叫的,所以當你覆寫 OnXyz() 方法時,
不要忘了呼叫 Control 類別的 OnXyz()(除非你有特殊需求),否則 Xyz 事件處理函式將會沒有作用。
只要呼叫 base.OnXyz(),就可以呼叫到 Control 類別的 OnXyz() 方法
1. 在 Main() 中,利用 Application.Run() 來將 Form1 視窗顯示出來,並進入訊息迴圈。程式的執行過程中,Application.Run() 一直未結束。
2. OS 在此 Process 的訊息佇列內放進一個 WM_PAINT 訊息,好讓視窗被顯示出來。
3. WM_PAINT 被 Application.Run() 內的訊息迴圈取出來,發派到 WndProc()。由於多型(Polymorphism)的因素,此次呼叫(invoke)到的 WndProc() 是屬於    Form1 的 WndProc(),也就是上述程式中批註(comment)1 的地方,而不是呼叫到 Control.WndProc()。
4. 在 Form1.WndProc() 的最後,有呼叫 base.WndProc(),這實際上呼叫到 Control.WndProc()。
5. Control.WndProc() 從 Message 引數中得知此訊息是 WM_PAINT,於是呼叫 OnPaint()。由於多型的因素,此次呼叫到的 OnPaint() 是屬於 Form1 的 OnPaint(),也就是上述程式中批註 2 的地方,而不是呼叫到 Control.OnPaint()。
6. 在 Form1.OnPaint() 的最後,有呼叫 base.OnPaint(),這實際上呼叫到 Control.OnPaint()。
7. 我們曾經在 Form1 的建構式(constructor)中將 Form1_Paint() 與 Form1_Paint2() 登記成為 Paint 事件處理函式(Event Handler)。Control.OnPaint() 會去依序去呼叫這兩個函式,也就是上述程式中批註 3 與 4 的地方。

【.NET Windows Message】
1.Control--Button,Form……
  protect vitrual WndProcess(ref Message);
    呼叫private Wm_(ref Message);//具體某類訊息
      呼叫Oprotect virtual On_xx(EventArg e);//觸發相關事件
2.解釋事件冒泡:比如鍵盤訊息可先由Form來處理,然後交由相關的Control來處理
3.解釋FormPaint:視窗的移動,最小化,最大話都會引起視窗內容的重新Paint,OS產生一個相關訊息發給應用程式的訊息佇列,應用程式得到並處理訊息時先是Form依次經過Wn_Process,Wn_..,On_Paint,Form_Paint,之後Form中的每一個Control也會依次做重繪的工作

http://www.cnblogs.com/jeemhu/archive/2009/05/09/1453297.html