windows 訊息迴圈的那些事

來源:互聯網
上載者:User

標籤:windows   ui   線程   訊息機制   訊息迴圈   

本文將結合網上的一些資料及自己的經驗、見解,對windows訊息機制進行簡單的剖析,有不對的地方歡迎指正哈!!


首先,指明一些訊息在windows系統中是什麼角色:

訊息是訊息機制中的郵件,用於背景工作執行緒與UI線程、表單與表單、一個進程對另一個線程的表單進行通訊。他是win32程式啟動並執行血液,通過訊息才能把整個系統關聯起來。

訊息對應於系統的一個UINT值,也即32位的無符號整形值,例如我們平時會自訂的WM_USER、WM_PAINT等等。它唯一的定義了一個事件,向 Windows發出一個通知,告訴應用程式某個事情發生了。例如,單擊滑鼠、改變視窗尺寸、按下鍵盤上的一個鍵都會使Windows發送一個訊息給應用程式的訊息佇列(下面會講到)中,然後應用程式再從訊息佇列中取出訊息並進行相應的響應。在這個處理的過程中,作業系統也會給應用程式“發送訊息”,而所謂的發送訊息--------實際上就是作業系統調用程式中的一個專門負責處理訊息的函數,這個函數稱為視窗過程。

這裡再點明一下,像滑鼠點擊事件、鍵盤事件等這些事件需要依賴系統的系統對這些硬體訊號轉化為具體的訊息,而這需要驅動層將這些硬體訊號轉化為事件通知核心,核心再轉化為訊息放到訊息佇列(後邊會將)中,這也就是大家一致所說的事件機制:硬體到訊息的轉化。

訊息本身是作為一個記錄傳遞給應用程式的,這個記錄中包含了訊息的類型以及其他資訊。例如,對於單擊滑鼠所產生的訊息來說,這個記錄中包含了單擊滑鼠時的座標。這個記錄類型叫做MSG,MSG含有來自windows應用程式訊息佇列的訊息資訊,在Windows中MSG結構體定義如下:

typedef struct tagMsg{       HWND    hwnd;            //接受該訊息的視窗控制代碼       UINT    message;         //訊息常量標識符,也就是我們通常所說的訊息編號       WPARAM  wParam;          //32位訊息的特定附加資訊,確切含義依賴於訊息值       LPARAM  lParam;          //32位訊息的特定附加資訊,確切含義依賴於訊息值       DWORD   time;            //訊息建立時的時間       POINT   pt;              //訊息建立時的滑鼠/游標在螢幕座標系中的位置}MSG;

hwnd是指訊息所屬的表單控制代碼(這裡先點出,其實訊息是屬於線程的,所以通過PostThreadMessage 向指定線程發送訊息的時候,這裡的hwnd可以為空白,表示訊息不屬於任何表單)

訊息佇列:系統啟動時就會建立系統訊息佇列,用來接收硬體事件轉化成的訊息,再由RIT線程將事件分化到各個應用線程對應的訊息佇列。每個UI線程都有一個訊息佇列,而不是每個表單一個訊息佇列!什麼是UI線程呢?只能簡單地說,當一個線程調用Win32 API中的GDI(Graphics Device Interface)和User函數時,作業系統才會將其看成是一個UI線程,並為它建立一個訊息佇列,這時候再添加線程的訊息處理迴圈就可以當時一個UI線程了。具體可以參考我轉載的一篇博文細說UI線程和Windows訊息佇列

1. 系統訊息佇列

當作業系統啟動並初始化時,線程Raw Input Thread(RIT)就會啟動,並創系統硬體輸入隊列(System Hardware Input Queue)(SHIQ). 對於外部的硬體事件(滑鼠或者鍵盤),硬體驅動會將事件轉換成訊息,並存放到SHIQ中,而RIT線程就專門負責處理SHIQ中的訊息,把訊息分發到對應線程的訊息佇列裡面。

2. 線程訊息佇列

對於每個用MFC開發的GUI程式,他們都有一個CMy***App,該類繼承自CWinApp,而CWinApp繼承自CWinThread, CWinApp是一個GUI線程,系統會為其維護一個THREADINFO結構,

 訊息佇列包含在一個叫THREADINFO的結構中,有四個隊列:

?
1234 Sent Message Queue     發送訊息佇列Posted Message Queue   登記訊息佇列Visualized Input Queue 輸入訊息佇列Reply Message Queue    響應訊息佇列

Sent Message Queue: 該隊列儲存其他程式通過SendMessage給該線程發送的訊息 
Posted Message Queue: 該隊列儲存其他隊列通過PostMessage給該線程發送的訊息 
Visualized Input Queue: 儲存系統隊列分發過來的訊息,比如滑鼠或者鍵盤的訊息 

Reply Message Queue: 儲存向表單發送訊息後的結果,比如sendMessage操作結束後,接收訊息方會發送一個Reply訊息給發送方的Reply隊列中,以喚醒發送隊列。

 這些隊列如何產生的?線程是核心對象,我們看看線程資訊是如何定義的,包含哪些內容,分析如下THREADINFO結構體定義,可以得出一些資訊:



訊息迴圈:當一個線程有屬於自己的隊列時,那麼線程需要有一個迴圈函數一直去訊息佇列中拿取訊息,並對訊息進行處理。通過vs建立的application工程中預設的主函數就包含這麼一段訊息迴圈函數(或許你會熟悉):
// Main message loop:while (GetMessage(&msg, NULL, 0, 0)){if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){TranslateMessage(&msg);DispatchMessage(&msg);}}

是不是有一種似曾相識的感覺:)
GetMessage(&msg, NULL, 0, 0)
 這一句是從訊息佇列中擷取一個訊息,NULL表示只要隊列裡有訊息就擷取,也可以指定擷取某個特定表單的訊息。這個函數是阻塞的,拿不到訊息就會一直等,直到有訊息為止。具體參見可看msdn GetMessage文檔拿到訊息後就是對訊息的處理了:調用TranslateAccelerator函數對快速鍵訊息進行轉化並發送出去,如果不成功再調用TranslateMessage函數將WM_KEYUP,WM_KEYDOWN之類的訊息轉化成相應的WM_CHAR之類的訊息。再調用DispatchMessage將訊息傳給各給表單的處理函數進行處理。那具體的處理函數在哪裡:1、首先我們可以通過GetMessage擷取到訊息後自己處理2、還記得那些年我們是怎麼註冊表單類的嗎?還記得註冊表單類需要傳一個LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)  函數嗎?這個函數就是表單訊息的處理函數:
////  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)////  PURPOSE:  Processes messages for the main window.////  WM_COMMAND- process the application menu//  WM_PAINT- Paint the main window//  WM_DESTROY- post a quit message and return////LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){int wmId, wmEvent;PAINTSTRUCT ps;HDC hdc;switch (message){case WM_COMMAND:wmId    = LOWORD(wParam);wmEvent = HIWORD(wParam);// Parse the menu selections:switch (wmId){case IDM_ABOUT:DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);break;case IDM_EXIT:DestroyWindow(hWnd);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}break;case WM_PAINT:hdc = BeginPaint(hWnd, &ps);// TODO: Add any drawing code here...EndPaint(hWnd, &ps);break;case WM_DESTROY:PostQuitMessage(0);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0;}

可能你會問,那上邊的訊息迴圈不是死迴圈嗎?那我們怎麼退出來?

你還記得我們是怎麼關閉視窗的嗎:通過調用Dialog::close() 函數其實會向表單發送一個 WM_DESTROY訊息,從上邊的代碼可以看到,處理這個訊息的邏輯其實就是 調用PostQuiteMessage函數,這個函數將像表單發送一個WM_QUIT訊息,仔細看一下GetMessage函數的返回值可以看出,只有GetMessage拿到這個訊息時會返回0,其他的都是非零值,所以此時訊息迴圈就退出了。

那麼訊息是怎麼根據表單控制代碼找到對應的線程的呢?

由於表單是一種特殊的控制代碼,屬於核心資源,在建立的時候系統就把他綁定到具體的某個線程控制代碼了,所以你才可以通過系統函數:HWND FindWindow(LPCSTR lpClassName,LPCSTR lpWindowName ); 根據表單名從核心中找到對應的控制代碼。而這也是為什麼前邊跟大家說的,訊息是屬於線程的,而不是屬於表單的,因為一個線程對應一個訊息佇列,對應多個表單。

知道怎麼收、處理訊息了,那可以怎麼發送訊息呢?Message函數家族:

SendMessage的原型如下:LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam),

這個函數主要是向一個或多個視窗發送一條訊息,一直等到訊息被處理之後才會返回。不過需要注意的是,如果接收訊息的視窗是同一個應用程式的一部分,那麼這個視窗的視窗函數就被作為一個子程式馬上被調用;如果接收訊息的視窗是被另外的線程所建立的,那麼視窗系統就切換到相應的線程並且調用相應的視窗函數,這條訊息不會被放進目標應用程式隊列中。函數的返回值是由接收訊息的視窗的視窗函數返回,返回的值取決於被發送的訊息。
     PostMessage的原型如下:BOOL PostMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam),

該函數把一條訊息放置到建立hWnd視窗的線程的訊息佇列中,該函數不等訊息被處理就馬上將控制返回。需要注意的是,如果hWnd參數為 HWND_BROADCAST,那麼,訊息將被寄送給系統中的所有的重疊的視窗和快顯視窗,但是子視窗不會收到該訊息;如果hWnd參數為NULL,則該函數類似於將dwThreadID參數設定成當前線程的標誌來調用PostThreadMEssage函數。


  從上面的這2個具有代表性的函數,我們可以看出訊息的發送方式和寄送方式的區別所在:被發送的訊息會被立即處理,處理完畢後函數才會返回;被寄送的訊息不會被立即處理,他被放到一個先進先出的隊列中,一直等到應用程式空線的時候才會被處理,不過函數放置訊息後立即返回。

  實際上,發送訊息到一個視窗處理過程和直接調用視窗處理過程之間並沒有太大的區別,他們直接的唯一區別就在於你可以要求作業系統截獲所有被發送的訊息,但是不能夠截獲對視窗處理過程的直接調用。
  以寄送方式發送的訊息通常是與使用者輸入事件相對應的,因為這些事件不是十分緊迫,可以進行緩慢的緩衝處理,例如滑鼠、鍵盤訊息會被寄送,而按鈕等訊息則會被發送。
  廣播訊息用得比較少,BroadcastSystemMessage函數原型如下:
       long BroadcastSystemMessage(DWORDdwFlags,LPDWORD lpdwRecipients,UINT uiMessage,WPARAM wParam,LPARAM lParam);該函數可以向指定的接收者發送一條訊息,這些接收者可以是應用程式、可安裝的驅動程式、網路驅動程式、系統層級的裝置驅動訊息和他們的任意組合。需要注意的是,如果dwFlags參數是BSF_QUERY並且至少一個接收者返回了BROADCAST_QUERY_DENY,則返回值為0,如果沒有指定BSF_QUERY,則函數將訊息發送給所有接收者,並且忽略其返回值。

BOOL PostThreadMessage(DWORD idThread,UINT Msg,WPARAM wParam,LPARAM IParam);向具體的線程發送訊息,可以不屬於哪個表單的訊息。這也使得訊息不僅僅是用於視窗介面的,而且用於整個系統的
MFC訊息機制:個人看法,MFC其實就是封裝了一些系統訊息API的,並添加一些訊息定義宏的庫,詳細資料可以參考博文MFC訊息機制。
寫這個主要是用於個人記筆記、並鍛煉寫文章的能力,有問題請輕噴:)





windows 訊息迴圈的那些事

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.