1. 程式人生 > >WINDOWS訊息處理過程

WINDOWS訊息處理過程

一、引言二、Windows訊息機制的概念

1
DOSWindows驅動機制的區別

2
、訊息

3
、訊息的來源

4
Windows的訊息系統的組成

5
、訊息的響應三、Windows訊息機制要點

1.
視窗過程

2
訊息型別

3
訊息佇列(Message Queues)

4
佇列訊息和非佇列訊息

5 Windows
訊息函式

6
訊息死鎖( Message Deadlocks

7 BroadcastSystemMessage

四、MFC訊息機制

1.MFC
框架下,接收處理來自Windows訊息的過程

2.MFC
內部訊息處理方式一、引言 C++程式架構一文中,我們看到,程式是由一些層次和模組組成的,那麼,這些模組之間,
以及你的程式和windows之間,是如何傳遞資訊呢?在windows的平臺上,傳遞資訊是由 windows message訊息機制來負責的,這是Windows的核心部分。訊息包括資料和指令。二、Windows訊息機制的概念

1
DOSWindows驅動機制的區別

1
DOS是過程驅動的。傳統的MS-DOS程式主要採用順序的。關聯的、過程驅動的程式設計方法。一個過程是一系列預先定義好的操作序列的組合,它具有一定的開頭、中間過程和結束。程式直接控制程式事件和過程的順序。這樣的程式設計方法是面向程式而不是面向使用者的,互動性差,使用者介面不夠友好,因為它強迫使用者按照某種不可更改的模式進行工作。它的基本模型如圖
1.1所示。

2
Windows是事件(訊息)驅動事件驅動程式設計是一種全新的程式設計方法,它不是由事件的順序來控制,而是由事件的發生來控制,而這種事件的發生是隨機的、不確定的,並沒有預定的順序,這樣就允許程式的的使用者用各種合理的順序來安排程式的流程。對於需要使用者互動的應用程式來說,事件驅動的程式設計有著過程驅動方法無法替代的優點。它是一種面向使用者的程式設計方法,它在程式設計過程中除了完成所需功能之外,更多的考慮了使用者可能的各種輸入,並針對性的設計相應的處理程式。它是一種被動式程式設計方法,程式開始執行時,處於等待使用者輸入事件狀態,然後取得事件並作出相應反應,處理完畢又返回並處於等待事件狀態。它的框圖如圖
1.2所示:

2
、訊息

Windows
系統是一個事件驅動的OS,每一個事件的發生都會產生一個訊息,我們通過訊息來知道發生了什麼事件,瞭解事件,進而解決事件。什麼是訊息呢?很難下一個定義,下面從不同的幾個方面講解一下:

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

   2
)誰將收到訊息:一個訊息必須由一個視窗接收。在視窗的過程(WNDPROC)中可以對訊息進行分析,對自己感興趣的訊息進行處理。例如你希望對選單選擇進行處理那麼你可以定義對WM_COMMAND進行處理的程式碼,如果希望在視窗中進行圖形輸出就必須對WM_PAINT進行處理。
   3
)未處理的訊息到那裡去了:M$為視窗編寫了預設的視窗過程,這個視窗過程將負責處理那些你不處理訊息。正因為有了這個預設視窗過程我們才可以利用Windows的視窗進行開發而不必過多關注視窗各種訊息的處理。例如視窗在被拖動時會有很多訊息傳送,而我們都可以不予理睬讓系統自己去處理。

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

3
、訊息的來源事件驅動圍繞著訊息的產生與處理展開,一條訊息是關於發生的事件的訊息。事件驅動是靠訊息迴圈機制來實現的。也可以理解為訊息是一種報告有關事件發生的通知。

Windows
應用程式的訊息來源有一下四種:

1
)輸入訊息:包括鍵盤和滑鼠的輸入。這一類訊息首先放在系統訊息佇列中,然後由Windows將它們送入應用程式訊息佇列中,由應用程式來處理訊息。

2
)控制訊息:用來與Windows的控制物件,如列表框、按鈕、檢查框等進行雙向通訊。當用戶在列表框中改動當前選擇或改變了檢查框的狀態時發出此類訊息。這類訊息一般不經過應用程式訊息佇列,而是直接傳送到控制物件上去。

3
)系統訊息:對程式化的事件或系統時鐘中斷作出反應。一些系統訊息,象DDE訊息(動態資料交換訊息)要通過Windows的系統訊息佇列,而有的則不通過系統訊息佇列而直接送入應用程式的訊息佇列,如建立視窗訊息。

  4
)使用者訊息:這是程式設計師自己定義並在應用程式中主動發出的,一般由應用程式的某一部分內部處理。

4
Windows的訊息系統的組成

Windows
的訊息系統由以下3部分組成:訊息佇列:Windows能夠為所有的應用程式維護一個訊息佇列,應用程式必須從訊息佇列中獲去訊息,然後分派給某個窗體。訊息迴圈:通過這個迴圈機制,應用程式從訊息佇列中檢索訊息,再把它分派給適當的視窗,然後繼續從訊息佇列中檢索下一條訊息,再分派給適當的視窗,依次進行。視窗過程:每個視窗都有一個視窗過程,以接收Windows傳遞給視窗的訊息,視窗過程的任務就是獲取訊息並且響應它。視窗過程是一個回撥函式,處理完一個訊息後,通常要給Windows一個返回值。

5
、訊息的響應訊息的產生來源於系統事情(包括計時器事件)和使用者事情,Windows用訊息來調入和關閉(還有其它處理,如繪製一個視窗等)應用程式,一個典型表現是在關機操作中,Windows發一個關機的訊息給所有正在執行的應用程式,告知它們退出記憶體,此時,應用程式用迴應訊息的方法來響應OS,因此,訊息是應用程式與WinOS互動的手段..

訊息產生到被視窗響應的步驟:(如下圖)

1>    
系統發生了或使用者發出某個事件。

2>     Windows
把這個事件翻譯為訊息,然後把他放到訊息佇列中

3>    
應用程式從訊息佇列中接受到這個訊息,把他存放到TMsg記錄中。

4>    
應用程式把訊息傳遞給一個適當的窗體過程。窗體過程響應這個訊息並進行處理。把訊息傳遞給了這個窗體的窗體函式。三、Windows訊息機制要點

1.
視窗過程每個視窗會有一個稱為視窗過程的回撥函式(WndProc),它帶有四個引數,分別為:視窗控制代碼(Window Handle),訊息ID(Message ID),和兩個訊息引數(wParam,lParam),當視窗收到訊息時系統就會呼叫此視窗過程來處理訊息。(所以叫回調函式)

2
訊息型別

1)
系統定義訊息(System-Defined Messages)

SDK中事先定義好的訊息,非使用者定義的,其範圍在[0×0000, 0×03ff]之間,可以分為以下三類:視窗訊息(Windows Message)

與視窗的內部運作有關,如建立視窗,繪製視窗,銷燬視窗等。可以是一般的視窗,可以是         Dialog,控制元件等。如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR,WM_HSCROLL..

命令訊息(Command Message)

與處理使用者請求有關,如單擊選單項或工具欄或控制元件時,就會產生命令訊息。

WM_COMMAND, LOWORD(wParam)
表示選單項,工具欄按鈕或控制元件的ID。如果是控制元件, HIWORD(wParam)表示控制元件訊息型別控制元件通知(Notify Message)

控制元件通知訊息,這是最靈活的訊息格式,Message, wParam,lParam分別為:WM_NOTIFY,控制元件ID,指向NMHDR的指標。NMHDR包含控制元件通知的內容,可以任意擴充套件。

2)
程式定義訊息(Application-Defined Messages)

使用者自定義的訊息,對於其範圍有如下規定:

 WM_USER: 0×0400-0×7FFF    (ex. WM_USER+10)

 WM_APP(winver>4.0): 0×8000-0xBFFF(ex.WM_APP+4)

RegisterWindowMessage: 0xC000-0xFFFF

3
訊息佇列(Message Queues)

Windows
中有兩種型別的訊息佇列

 1)
系統訊息佇列(System MessageQueue)

這是一個系統唯一的Queue,裝置驅動(mouse,keyboard)會把操作輸入轉化成訊息存在系統佇列中,然後系統會把此訊息放到目標視窗所在的執行緒的訊息佇列(thread-specificmessage queue)中等待處理

2)
執行緒訊息佇列(Thread-specific Message Queue)
每一個GUI執行緒都會維護這樣一個執行緒訊息佇列。(這個佇列只有在執行緒呼叫GDI函式時才會建立,預設不建立)。然後執行緒訊息佇列中的訊息會被送到相應的視窗過程(WndProc)處理.

注意:執行緒訊息佇列中WM_PAINTWM_TIMER只有在Queue中沒有其他訊息的時候才會被處理,WM_PAINT訊息還會被合併以提高效率。其他所有訊息以先進先出(FIFO)的方式被處理。

4
佇列訊息(Queued Messages)和非佇列訊息(Non-QueuedMessages)
1)
佇列訊息(Queued Messages)

訊息會先儲存在訊息佇列中,訊息迴圈會從此佇列中取訊息並分發到各視窗處理如滑鼠,鍵盤訊息。

2)
非佇列訊息(NonQueued Messages)

訊息會繞過系統訊息佇列和執行緒訊息佇列直接傳送到視窗過程被處理如: WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR WM_WINDOWPOSCHANGED

注意: postMessage傳送的訊息是佇列訊息,它會把訊息Post到訊息佇列中; SendMessage傳送的訊息是非佇列訊息,被直接送到視窗過程處理

5 Windows
訊息函式

1
PostMessage(PostThreadMessage), SendMessage

PostMessage:
把訊息放到指定視窗所在的執行緒訊息佇列中後立即返回。PostThreadMessage:把訊息放到指定執行緒的訊息佇列中後立即返回。

SendMessage
:直接把訊息送到視窗過程處理,處理完了才返回。

2
GetMessage, PeekMessage

PeekMessage
會立即返回可以保留訊息

GetMessage
在有訊息時返回會刪除訊息

3
TranslateMessage, TranslateAccelerator

TranslateMessage:
把一個virtual-key訊息轉化成字元訊息(character message),並放到當前執行緒的訊息佇列中,訊息迴圈下一次取出處理。

TranslateAccelerator:
將快捷鍵對應到相應的選單命令。它會把WM_KEYDOWN WM_SYSKEYDOWN轉化成快捷鍵表中相應的WM_COMMANDWM_SYSCOMMAND訊息,然後把轉化後的 WM_COMMANDWM_SYSCOMMAND直接傳送到視窗過程處理,處理完後才會返回。

6
訊息死鎖( Message Deadlocks

假設有執行緒AB現在有以下下步驟

1
執行緒A SendMessage給執行緒B, A等待訊息線上程B中處理後返回

2
)執行緒B收到了執行緒A發來的訊息,並進行處理,在處理過程中,B也向執行緒A SendMessgae,然後等待從A返回。因為此時,執行緒A正等待從執行緒B返回,無法處理B發來的訊息,從而導致了\執行緒A,B相互等待,形成死鎖。多個執行緒也可以形成環形死鎖。可以使用 SendNotifyMessageSendMessageTimeout來避免出現死鎖。

7 BroadcastSystemMessage

我們一般所接觸到的訊息都是傳送給視窗的,其實,訊息的接收者可以是多種多樣的,它可以是應用程式(applications),可安裝驅動(installable drivers),網路裝置(networkdrivers),系統級裝置驅動(system-level device drivers)等,

BroadcastSystemMessage
這個API可以對以上系統元件傳送訊息。那麼這些訊息是怎樣傳送的呢。我們以MFC為例來看一下訊息傳送過程。四、MFC訊息機制Windows應用程式的主函式中,首先要註冊視窗類,然後建立並顯示視窗。建立視窗後程序就進入訊息迴圈,在訊息迴圈中,程式不斷地獲得訊息並將訊息派送給對應的視窗函式進行處理。我們可以看到,在MFC的框架結構下,可以進行訊息處理的類的標頭檔案裡面都會含有DECLARE_MESSAGE_MAP()巨集,這裡主要進行訊息對映和訊息處理函式的聲明。可以進行訊息處理的類的實現檔案裡一般都含有如下的結構。

BEGIN_MESSAGE_MAP(CInheritClass, CBaseClass) //{{AFX_MSG_MAP(CInheritClass)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

這裡主要進行訊息對映的實現和訊息處理函式的實現。所有能夠進行訊息處理的類都是基於CCmdTarget類的,也就是說CCmdTarget
類是所有可以進行訊息處理類的父類。CCmdTarget類是MFC處理命令訊息的基礎和核心。同時MFC定義了下面的兩個主要結構:
AFX_MSGMAP_ENTRY
struct AFX_MSGMAP_ENTRY
{//“““//

};

AFX_MSGMAP

struct AFX_MSGMAP

{//“““`//
};

  
其中AFX_MSGMAP_ENTRY結構包含了一個訊息的所有相關資訊,AFX_MSGMAP主要作用是兩個,一:用來得到基類的訊息對映入口地址。二:得到本身的訊息對映入口地址。實際上,MFC把所有的訊息一條條填入到AFX_MSGMAP_ENTRY結構中去,形成一個數組,該陣列存放了所有的訊息和與它們相關的訊息的引數。同時通過AFX_MSGMAP能得到該陣列的首地址,同時也得到基類的訊息對映入口地址,這是為例當本身對該訊息不響應的時候,就呼叫其基類的訊息響應。現在我們來分析MFC是如何讓視窗過程來處理訊息的,實際上所有MFC的視窗類都通過鉤子函式_AfxCbtFilterHook截獲訊息,並且在鉤子函式_AfxCbtFilterHook中把視窗過程設定為AfxWndProc。原來的視窗過程儲存在成員變數m_pfnSuper

1.MFC
框架下,接收處理來自Windows訊息的過程:

2.MFC
內部訊息處理方式

MFC
接收一個寄送的訊息:

MFC
處理一個寄送和傳送訊息的惟一明顯不同是寄送的訊息要做應用程式的訊息佇列中花費一些時間。在訊息泵(message pump)彈出它之前,它要一直在佇列中。下面是怎樣接受寄送訊息的過程。MFC應用程式中的訊息泵在CWinApp的成員函式Run()中。應用程式開始執行時,Run()就被呼叫,Run()把時間分割成兩部分。一部分用來執行後臺處理,如取消臨時CWnd物件;另一部分用來檢查訊息佇列。當一個新的訊息進來時,Run()抽取它即用GetMessage( )從佇列中取出該訊息,執行PreTranslateMessage()::TranslateMessage( )兩個訊息翻譯函式,然後用DispatchMessage()函式呼叫該訊息預期的目標視窗程序。如下圖。我們用一個例項函式A傳送訊息到函式B的過程來看一下MFC內部訊息處理過程。

1.                          
首先函式A應獲取訊息的CWnd類物件的指標,然後,呼叫CWnd的成員函式SendMessage()。

LRESULT Res=pWnd->SendMessage(UINT Msg, WPARAM wParam, LPARAM lParam);

   pWnd
指標指向目標CWnd類物件。變數Msg是訊息,wParamlParam變數包含訊息的引數,如滑鼠單單擊哪裡或選擇了什麼選單項。目標視窗返回的訊息結果放在變數Res中。傳送訊息到一個沒有CWnd的函式物件的視窗,可以用下列目標視窗的控制代碼直接呼叫Windows API

LRESULT Res=::SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
    
這裡的hWnd是目標視窗的控制代碼。如果是非同步傳輸也可使用PostMessage(),訊息同上相同,但返回值Res不一樣,Res不是一個由目標窗體返回的值,而是一個布林值,用來表示訊息是否成功的放到訊息佇列中。

2.  
正常情況下,一旦訊息被髮送後,應用程式後臺會自動的將它傳送,但是在特殊情況下,需要你自己去刪除一個訊息,例如想在應用程式接收到某種訊息之前停止應用程式。有兩種方法可以從應用程式訊息佇列中刪除一個訊息,但這兩種方法都沒有涉及MFC第一種方法:在不干擾任何事情之下窺視訊息佇列,看看一個訊息是否在那裡。
    BOOL res=::PeekMessage(LPMSG lpMsg,HWND hWnd, UINT wMsFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg ) ;
  
第二種方法:實際上是等待,一直等到一個新的訊息到達佇列為止,然後刪除並返回該訊息。
    BOOL res=::GetMessage(LPMSG lpMsg,HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax);
   
在這兩種方法中,變數hWnd指定要截獲訊息的視窗,如果該變數設為NULL,所有視窗訊息將被截獲。wMsgFilterMinwMsgFilterMax變數與SendMessage( )中的變數Msg相對應,指定檢視訊息的範圍。如果用“0,0〃,則所有的訊息都將被截獲。如果用WM_KEYFIRST,WM_KEYLASTWM_MOUSEFIRST,WM_MOUSELAST,則所有鍵盤或滑鼠的訊息將被截獲。wRemoveMsg變數指定PeekMessage( )是否應該真正地從佇列中刪除該訊息。(GetMessage( )總是刪除訊息)。該變數可以取兩個值:
      PM_REMOVE
PeekMessage( )將刪除訊息。
      PM_NOREMOVE
PeekMessage( )將把訊息留在佇列裡,並返回它的一個拷貝。當然,如果把訊息留在訊息佇列中,然後再次呼叫PeekMessage( )檢視相同型別的訊息,則將返回完全相同的訊息。
    lpMsg
變數是一個指向MSG結構的指標,MSG包含檢索到的訊息。
    typedef struct tagMSG {
                        HWND hwnd; //window handle message is intended for
                        UINT message;
                        WPARAM wParam;
                        LPARAM lParam;
                        DWORD time; //the time the message was put in the queue
                        POINT pt; // thelocation of the mouse cursor when the
                                       //message was put in the queue
                        } MSG;

3.  
訊息會到訊息佇列中。CwinApp的成員函式Run,在應用程式執行時,Run就把時間分割成兩部分,一部分執行後臺的處理,另一部分來檢查訊息的佇列,當發現新訊息時,Run就呼叫GetMessage()從佇列訊息中取出該訊息。

3.
執行兩個訊息翻譯函式PreTranslateMessage()和::TranslateMessage(),進行翻譯。主要找到函式物件的位置、訊息的動作標識和跟訊息相關的執行操作。

4.
DispatchMessage()函式呼叫該訊息預期的函式B程序,進行執行。