關於windows作業系統之訊息和訊息佇列

來源:互聯網
上載者:User

標籤:windows

關於訊息和訊息佇列
不像基於MS-DOS的應用程式,基於Windows的程式是事件驅動的。他們不做任何顯示調用來擷取輸入。而是通過等待系統傳遞給他們。


系統為應用程式傳遞所有輸入到程式中的不同視窗。每個視窗都有一個稱為視窗過程的函數,用於處理所有到該視窗的輸入。視窗處理過程處理輸入,並將控制返回給系統。


如果一個頂層視窗停止回應訊息超過兩秒,系統將會認為該視窗為非響應狀態。在這種情況下,系統將隱藏該視窗並用擁有同樣Z順序,位置,尺寸和可視化屬性的ghost視窗替代該視窗。這種情況下,允許使用者移動它,或者改變他的尺寸,甚至關閉應用程式。然後,這也是僅僅可以做的動作,因為應用程式現在是不響應的。當在調試狀態下,系統不會產生ghost視窗。


這個段落,討論如下主題:
windows訊息
系統以訊息的形式傳遞輸入到視窗的處理過程。系統和應用程式均可產生訊息。系統在每次輸入事件時,產生一個訊息,比如,當用於敲擊,移動滑鼠或者點擊捲軸一類的控制項。應用程式引起系統改變也會導致系統產生訊息,比如一個應用程式改變了系統的字型資源池或者改變了他自己視窗的大小。一個應用程式可以產生這樣的訊息,該訊息可以引導他的視窗直接執行任務或者和其他應用程式的視窗進行互動。


訊息分類:
系統定義訊息
當系統和應用程式互動時,系統發送系統訊息,以控制應用程式的操作以及給程式傳遞輸入或者其他訊息。應用程式也可以發送系統訊息,應用程式通常用這些訊息來控制通過預先註冊的視窗類別創造的視窗的行為。


訊息常量標記指定了其所屬系統預定義訊息種類。首碼確定可以翻譯或者處理的訊息種類。如下。
AMB/ABN ===application desktop toolbar
acm/acn ===animation control
cb/cbn  ===combobox control
ccm ===generatl control
cdm ===common dialog box
dfm ===default contex menu
dl ===drag list box
sb ===status bar
tvm/tvn ===tree view contro
udm/udm === up-down controm
wm === general
......
tcm/tcn === tab control
{
Clipboard Messages Clipboard Notifications Common Dialog Box Notifications Cursor Notifications Data Copy Message Desktop Window Manager Messages Device Management Messages Dialog Box Notifications Dynamic Data Exchange Messages Dynamic Data Exchange Notifications Hook Notifications Keyboard Accelerator Messages Keyboard Accelerator Notifications Keyboard Input Messages Keyboard Input Notifications Menu Notifications Mouse Input Notifications Multiple Document Interface Messages Raw Input Notifications Scroll Bar Notifications Timer Notifications Window Messages Window Notifications 
}


大體上,windows訊息覆蓋了一個比較寬的範圍,包括滑鼠鍵盤,菜單,對話方塊輸入,視窗建立管理,DDE動態資料交換




應用程式定義的訊息
應用程式可以建立訊息,其自身視窗可以使用,也可以用於和其他進程進行互動。


訊息標記符的值應用如下:
1.系統保留了0x0000-0x03ff(即wm_user-1),應用程式不可以使用這些值用於私人訊息
2.0x0400(WM_USER)-0x7fff可以用於私人訊息
3.如果應用程式在4.0系統上,你可以使用0x8000(wm_app)-0xbfff於私人訊息
4.RegisterWindowMessage返回的值在0XC000-0XFFFF之間。這個函數的返回值,可以避免其他進程用同樣值而引起的衝突


訊息路由
使用使用兩種方式來視窗過程訊息的路線:post類訊息是通過先進先出的訊息佇列方式,訊息佇列是臨時儲存訊息的系統定義記憶體對象,以及sending類訊息直接到達視窗過程。


隊列訊息1
系統在同一時間可以顯示任意數量的視窗。為了路由滑鼠鍵盤輸入到正確的視窗,系統採用了訊息佇列。


系統維護了一個系統訊息佇列,並為每個GUI線程維護了而一個線程專有訊息佇列。為了避免為非GUI線程過多建立訊息佇列,所有線程在建立時沒有訊息佇列。系統僅僅線上程第一次發起某個專門使用者函數時,建立線程訊息佇列;沒有GUI函數調用將引起訊息佇列的建立。


未懂:
The system creates a thread-specific message queue only when the thread makes its first call to one of the specific user functions; no GUI function calls result in the creation of a message queue.


隊列訊息2
任何時候,使用者移動滑鼠,點擊按鈕或者敲擊鍵盤,滑鼠或者鍵盤驅動將轉換這些輸入為訊息,並將它們放到系統訊息佇列中。系統在檢測它們的目視窗時,同時從系統訊息佇列中移除它們。然後將他們發送到訊息相關視窗的視窗建立線程。線程從它們的訊息佇列中接收所有滑鼠和鍵盤訊息。線程從它們的隊列中刪除訊息,並指引系統將它們發送到正確的視窗過程進行處理。




除了WM_PATIN,WM_TIMER,WM_QUIT訊息外,系統一直將它們發送到訊息佇列的末尾,以確保輸入訊息的FIFO序列,僅當訊息對用中沒有其他訊息事後,WM_PATIN,WM_TIMER,WM_QUIT才被向前推至視窗處理過程。再就是,多個WM_PAINT訊息將被合并為一個,確定所有用戶端無效地區到一個單獨的地區。合并WM_PATINT就是為了減少視窗沖回客戶區內容的次數。


從訊息佇列中刪除一個訊息後,應用程式將用DispatchMessage函數direct系統發送這個訊息到視窗處理過程以緊湊處理。DispatchMessage沒有發送訊息位置和時間到視窗過程,應用程式可以通過GetmessageTime和GetMessagePos函數。


當訊息佇列中沒有訊息的時候,線程可以使用WaitMessage函數來將控制器交給其他線程,這個函數暫停線程,知道一個新訊息到來,該函數才返回。


你也可以調用SetMessageExtraInfo來為當前訊息佇列附加一個值,通過GetMessageExtraInfo來擷取這個值。


非隊列訊息
繞過了系統和線程訊息佇列,非隊列訊息直接發送至視窗過程。系統典型發送非隊列訊息來通知一個視窗,一個事件影響了它。例如,當使用者啟用一個新視窗,系統發送給視窗 WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR訊息。這些訊息通知視窗它已經被啟用了,鍵盤輸入正指向該視窗,滑鼠游標已經移至了視窗邊框內。當應用程式調用某些系統函數時,也會視窗非隊列訊息,比如,應用程式在調用SetWindowPos時,系統將發送WM_WINDOWPOSCHANGED訊息。


有些訊息發送非隊列訊息:BroadcastSystemMessage, BroadcastSystemMessageEx, SendMessage, SendMessageTimeout, and SendNotifyMessage. 


訊息處理
多線程應用程式,會在每個建立了視窗的線程包含一個訊息佇列。




MSG msg;
BOOL bRet;


while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)

    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else
    {
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    }
}
一個應用程式可以通過調用PostQuitMessage來結束其自身的訊息迴圈,響應應用程式主視窗的WM_DESTROY訊息,就比較典型。


PostMessage發送一個NULL視窗控制代碼的訊息,該訊息將會被放在當前線程訊息佇列中,應用程式必須處理這個訊息。PostMessage也可以通過HWND_TOPMOST 控制代碼來給所有頂層視窗發送訊息。


PostMessage一直能夠成功發送訊息,通常是一個錯誤的假設,比如訊息佇列是滿的。一個應用程式應該核查PostMessage的返回值。如果失敗了,需要重新發送訊息。


SendMessage通常使用者父子視窗之間的互動。


SendMessageCallback函數發送一個訊息,並立即返回,視窗過程在處理完這個訊息後,系統將調用指定的回呼函數。該回呼函數的具體,請看SendAsyncProc


偶爾,你可能想向所有頂層視窗發送訊息。例如,應用程式改變了時間,可以通過SendMessage,並制定HWND_TOPMOST,發送WM_TIMECHANGE.你也可以通過BroadcastSystemMessage函數,並給lpdwRecipients參數制定BSN_APPLICATIONS


訊息死結
1.SendMessage會等待視窗過程處理完畢後才返回,如果視窗過程此時所線上程激昂控制權放棄,那麼僵早晨死結。
2.如果接收線程附加到了和發送線程同一個訊息佇列,也將導致應用程式死結的發送


注意,正在接收訊息的線程,不應該顯示放棄控制權;調用下面函數將引起線程隱私放棄控制權。
DialogBox 
?DialogBoxIndirect 
?DialogBoxIndirectParam 
?DialogBoxParam 
?GetMessage 
?MessageBox 
?PeekMessage 
?SendMessage 


為了避免潛在死結,考慮使用SendNotifyMessage或者SendMessageTimeout。要不然,視窗過程可以通過InSendMessage或者InSendMessageEx檢測其接收到的訊息是否來自其他線程.在處理一個訊息時,在調用上面列表中任何函數前,視窗過程應該調用InSendMessage(Ex).如果返回TRUE,視窗過程必須在yeild前,調用ReplyMessage函數。


系統廣播訊息-略






總結:
1.訊息分為系統定義訊息和使用者自訂訊息,其ID值皆有自己的範圍。
2.每個線程預設是沒有訊息佇列的,線程只有在第一次調用使用者介面時(比如建立視窗),系統才為其建立訊息佇列。
3.系統自身維護一個系統訊息佇列,然後還為每個GUI線程線程維護一個線程專門訊息佇列。
4.滑鼠、鍵盤等驅動,首先將事件轉換為訊息放置在系統訊息佇列中,然後系統又通過視窗來確定將其放入到哪個線程訊息佇列中。
5.線程訊息迴圈取出訊息,進行處理,將訊息再派發給系統,系統調用訊息對應的視窗過程。
6.PostMessage不一定成功,比如隊列是滿的。
7.避免訊息死結,比如接收訊息的視窗過程,在棄權前,需要檢測訊息是否發自其它線程。否則其它線程將長時間等待。其實我感覺這裡不能成為死結嘛,畢竟還是可能再執行的,只是時間長短而已。
8.需要注意wm_paint,wm_timer,wm_quit等特殊訊息
9.系統預定義訊息其實大都是那些控制項訊息,通知訊息,系統廣播訊息等等。




訊息相關函數:
---
DispatchMessage


LONG  DispatchMessage( 
  const MSG* lpmsg 
);
1.該函數將訊息,通過系統派發給視窗過程
2.如果是一個定時器訊息,lParam參數不是空, lParam指向一個函數地址,被調用的將是這個函數,而非視窗過程
--- 


 GetMessage
應用程式使用該函數返回值來決定是否終止訊息迴圈,並退出程式。
該函數將擷取和hWnd或者其子視窗相關的訊息。
---
 DWORD GetMessagePos(void);
 該函數返回訊息x,y座標,在多重monitor下,可能有負值。
 ---


 GetMessageQueueReadyTimeStamp
 擷取線程最近一次準備處理一個訊息的系統時間(GetTickCount)
 ---
 


 GetMessageSource
 MSGSRC_SOFTWARE_POST表面鍵盤訊息來自software(postmessage標記為software). MSGSRC_HARDWARE_KEYBOARD 表面訊息來自keyboard. MSGSRC_UNKNOWN 訊息來源未知
---
DWORD GetQueueStatus(
  UINT flags
);
在訊息佇列中的訊息的類型 
flags為要檢測的訊息類型。
返回值得高位元組表示當前在訊息佇列中的訊息類型。低位元組表示從上次GetQueueStatus,GetMessage或者PeekMessage後被排入佇列的訊息類型。
--- 
 InSendMessage
用於判斷當前視窗過程所處理的訊息,是否來自其他線程的SendMessage調用。
 ---
 PeekMessage
1.該函數核查線程訊息佇列中是否有訊息,並將訊息放在參數結構體中
2.如果hWnd參數=-1,則只返回hWnd=NULL的訊息,這種訊息來自PostThreadMessage
3.參數wRemoveMsg需要注意
4.如果應用程式正在建立頂層視窗時調用PeekMessage,將導致視窗視窗被建立在Z-Order的最後。你需要在PeekMessage後,顯式調用SetForegroundWindow。如果應用程式以及有一個前置視窗了,那麼新視窗將被前置。
---
 PostMessage
 應用程式要用HWND_BROADCAST進行程式間的互動,訊息應該擷取於RegisterWindowMessage()
 
 如果發送訊息低於WM_USER範圍,到非同步訊息佇列函數(PostMessage、SendNotifyMessage),訊息參數不應該包含指標,不然的話,操作將失敗。該函數將在接收線程有機會處理該訊息前返回,寄件者將釋放剛剛用到的記憶體。
 ---


 PostQuitMessage
 該函數只是簡單表明被請求終止的線程將會終止。接收WM_QUIT的線程,應該終止訊息迴圈,並將控制權交給系統。返回給系統的退出值,一定是WM_QUIT的wParam參數
---
 
BOOL PostThreadMessage(
  DWORD idThread, 
  UINT Msg, 
  WPARAM wParam, 
  LPARAM lParam 
); 
 
接收訊息的線程,通過GetMessage/PeekMessage來擷取訊息,hWnd成員將會是空
 
---
 RegisterWindowMessage
 同一字串,註冊的值,在整個系統中是唯一的
 
---
 SendMessage
非訊息佇列方式,直接調用視窗過程,系統立即切換到接收線程執行,發送線程鎖住,知道接收線程處理完畢
 ----


 SendMessageTimeout
 該函數通過調用視窗過程的方式發送訊息,如果視窗屬於不同線程,SendMessageTimerout將知道訊息處理完畢才返回或者指定的逾時已經過去,如果視窗就在當前線程,則直接調用視窗過程,並忽略time-out逾時
--- 


 SendNotifyMessage
 如果視窗建立於屬於發送訊息的線程,則調用視窗過程,並等待視窗過程處理完畢該訊息。如果是不同線程,則將訊息傳遞到視窗過程,並立即返回,不等待視窗過程的訊息處理過程。
 
--
 TranslateMessage
1.將虛擬鍵訊息轉換為字元訊息,然後將字元訊息發送到調用線程的訊息佇列中,該字元訊息將在下次調用GetMessage或者PeekMessage訊息的時候擷取到。
2.WM_(SYS)KEYDOWN/UP--->WM_(SYS)_CHAR
3.如果應用程式為了其他目的,處理虛擬鍵訊息,那麼就不應該調用TranslateMessage.與一個執行個體,應用程式不應該在TranslateAccelerator函數返回非0值時調用TranslateMessage

關於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.