1. 程式人生 > >Windows訊息佇列、執行緒訊息佇列,視窗訊息的概念與關係

Windows訊息佇列、執行緒訊息佇列,視窗訊息的概念與關係

1.視窗
Windows程式是由一系列的視窗構成的,每個視窗都有自己的視窗過程,視窗過程就是一個擁有有固定 Signature 的 C函式,具體格式如下:
LRESULT CALLBACK WindowProc(HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);


視窗型別:
可重疊視窗(Overlapped Window),
彈出視窗(Pop-up Window),
子視窗(Child Window)


視窗之間的關係: 父子關係,擁有關係,前後關係。


2.執行緒

一個程序至少擁有一個執行緒,稱為主執行緒,如果一個執行緒建立了視窗,擁有GUI資源,那麼也稱該執行緒為GUI執行緒,否則就為工作執行緒。視窗是由執行緒建立的,
建立視窗的執行緒就擁有該視窗。這種執行緒擁有關係的概念對視窗有重要的意義:建立視窗的執行緒必須是為視窗處理所有訊息的執行緒。為了使這個概念更加明
確具體,可以想像一個執行緒建立了一個視窗,然後就結束了。在這種情況下,視窗不會收到一個WM_DESTROY或WM_NCDESTROY訊息,因為執行緒已經結束,不可能被用來使視窗接收和處理這些訊息。每個執行緒,如果它至少建立了一個視窗,都由系統對它分配一個訊息佇列。這個佇列用於視窗訊息的派送(dispatch)。為了使視窗接收這些訊息,執行緒必須有它自己的訊息迴圈,訊息迴圈一般如下:



MSG msg;
while( GetMessage(&msg, NULL, 0, 0) )
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}


應用程式不斷的從訊息佇列中獲取訊息,然後系統通過DispatchMessage函式分派訊息到相應視窗的視窗過程,使得訊息得到處理。當獲取到WM_QUIT訊息時,GetMessage返回0,迴圈結束。


3.訊息
訊息,就是指Windows發出的一個通知,告訴應用程式某個事情發生了。例如,單擊滑鼠、改變視窗尺寸、按下鍵盤上的一個鍵都會使Windows傳送一個訊息

給應用程式,它被定義為:
typedef struct {
HWND hwnd; //視窗控制代碼, 發生在哪個視窗上
UINT message; //訊息標識號 ( WM_MOUSEMOVE, WM_LBUTTONDOWN, ... )
WPARAM wParam; //訊息引數1
LPARAM lParam; //訊息引數2
DWORD time;
POINT pt;
} MSG, *PMSG;
一個訊息結構體包含了該事件 所有完備資訊,當應用程式收到該訊息時,就可以做出相應處理了。


訊息分類:
<1>.佇列訊息和非佇列訊息
從訊息的傳送途徑上看,訊息分兩種:佇列訊息和非佇列訊息。
佇列訊息送到系統訊息佇列,然後到執行緒訊息佇列;非佇列訊息直接送給目的視窗過程。
這裡,對訊息佇列闡述如下:
Windows維護一個系統訊息佇列(System message queue),每個GUI執行緒有一個執行緒訊息佇列(Thread message queue)。滑鼠、鍵盤事件由滑鼠或鍵盤驅動程式轉換成輸入訊息並把訊息放進系統訊息佇列,例如WM_MOUSEMOVE、WM_LBUTTONUP、WM_KEYDOWN、WM_CHAR等等。Windows每次從系統訊息佇列移走一個訊息,確定它是送給哪個視窗的和這個視窗是由哪個執行緒建立的,然後,把它放進視窗建立執行緒的執行緒訊息佇列。執行緒訊息佇列接收送給該執行緒所建立視窗的訊息。執行緒從訊息佇列取出訊息,通過Windows把它送給適當的視窗過程來處理。


除了鍵盤、滑鼠訊息以外,佇列訊息還有WM_PAINT、WM_TIMER和WM_QUIT。這些佇列訊息以外的絕大多數訊息是非佇列訊息。


<2>.系統訊息和應用程式訊息
從訊息的來源來看,可以分為:系統定義的訊息和應用程式定義的訊息。
系統訊息ID的範圍是從0到WM_USER-1,或0X80000到0XBFFFF;應用程式訊息從WM_USER(0X0400)到0X7FFF,或0XC000到0XFFFF;WM_USER到0X7FFF範圍的訊息
由應用程式自己使用;0XC000到0XFFFF範圍的訊息用來和其他應用程式通訊,為了ID的唯一性,使用::RegisterWindowMessage來得到該範圍的訊息ID。


<3>.視窗訊息,命令訊息,控制元件通知訊息
根據處理過程的不同,可以分為三類:視窗訊息,命令訊息,控制元件通知訊息。


(1).視窗訊息
一般以WM_開頭,如WM_CREATE, WM_SIZE, WM_MOUSEMOVE等標準的Windows訊息, 用於視窗相關的事件通知,視窗訊息將由系統分配到該視窗的視窗過程處理。
(2).命令訊息 (WM_COMMAND)
一種特殊的視窗訊息,它從一個視窗傳送到另一個視窗以處理來自使用者的請求,通常是從子視窗傳送到父視窗,例如,點選按鈕時,按鈕的父視窗會收到
WM_COMMAND訊息,用以通知父視窗按鈕被點選,經測試:子視窗向父視窗傳送WM_COMMAND訊息,或者稱為父視窗會收到WM_COMMAND訊息,作業系統並不是通過將WM_COMMAND訊息放入到父視窗的訊息佇列中去,而是直接呼叫了父視窗的視窗過程,以 WM_COMMAND 為訊息標識引數(UINT uMsg),實現這個功能的API函式正是: LRESULT DispatchMessage(const MSG *lpmsg);
(3).控制元件通知訊息
WM_NOTIFY訊息,當用戶與控制元件互動(Edit, Button...)時,通知訊息會從控制元件視窗傳送到父視窗,這種訊息的目的不是為了處理使用者命令,而是為了讓父窗
口能夠適時的改變控制元件。


windows 訊息處理機制是這樣的 :
首先 Windows OS 把來自硬體 ( 滑鼠 , 鍵盤等訊息 ) 和來自應用程式的訊息放到一個 OS 系統訊息佇列中去 ,而應用程式需要有自己的訊息佇列 , 也就是執行緒訊息佇列,每一個執行緒有自己的訊息佇列,對於多執行緒的應用程式就有和執行緒數目相等的執行緒訊息佇列 .
windows 訊息佇列把得到的訊息傳送到執行緒訊息佇列 , 執行緒訊息佇列每次取出一條訊息傳送到指定視窗 , 不斷迴圈直到程式退出 . 這個迴圈就是靠訊息環
while(GetMessage() )
{
TranslateMessage();
DispatchMessage();
}
實現的 .GetMessage() 只是從執行緒訊息中取出一條訊息 , 而 DispatchMessage
則把取出的訊息傳送到目的視窗 . 如果收到 WM_CLOSE 訊息則結束迴圈 , 傳送 PostQuitMessage(0), 處理WM_DESTROY 銷燬視窗
Windows 訊息佇列 UI 執行緒,視窗以及訊息處理方式總結
1 Windows UI thread:
2 每個窗體是一個 UI thread ,還是隻有一個 UI thread?答: 所有的窗體都用這一個 UI thread
3 Message queue: 每個窗體都有一個 message queue ,還是共用一個 message queue? 答:共用
4 message 處理:是同步還是非同步。是每次處理一個訊息等這個訊息處理完後再處理另一個訊息還是每次取一個不等這個訊息處理完就處理下一個,也就是 dispatchmessage 什麼時候返回?
答案是要等這個訊息處理函式處理完以後才返回,否則會造成訊息處理的混亂
5 message 的結構是什麼:包含了窗體的 handle ,訊息的型別等
在 Winuser.h 中有定義如下:
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
其中的成員變數 message 是 WM_SIZE 、 WM_COMMAND 、 WM_QUIT 等等訊息標識。
hwnd 中是這個訊息所在的視窗控制代碼。
6 Windows 如何知道訊息應該送到哪一個執行緒,
這裡我們要分為兩種情況 , 訊息是不是佇列訊息 , 比如在一個窗體空白處點選左鍵 , 首先 OS 會根據當前的context 來生成 MSG , MSG 中會包括要傳送到的視窗的 Handle, 這就是一個佇列訊息 , 首先 OS 會將這個訊息放到 OS 的系統訊息佇列中 , 而後 OS 會有專門的程序根據 MSG 中的視窗的 Handle 找到建立該視窗的執行緒,而後將該 MSG 送到該執行緒的訊息佇列,而後由該訊息迴圈來處理這個訊息, 最終由DispatchMessage 函式來將這個訊息送到相應的視窗處理函式。
如果你在一個窗體上點選了一個 button 呢,訊息的路徑是怎樣的呢?當你點選了一個 button 後, OS 產生三個MSG。 WM_LBUTTONDOWN和WM_LBUTTONUP,這兩個訊息的視窗Handle為button的handle。一個WM_command 或者 wm_notify 訊息, OS 會將這個訊息直接送給包含 button 的 window processdure 來處理,而不會將這個送到訊息佇列。