這篇文章解釋了GetMessage和PeekMessage的內部運做方式,同時也是一類與「消息及消息在16位 MS-DOS?/Microsoft? Windows?環境之下的影響」相關文章的基礎。咱們將討論下面這些主題:編程
·系統和應用程序隊列(譯者注:如下簡稱爲「程序隊列」)網絡
·GetMessage和PeekMessage函數ide
·消息過濾函數
·WM_QUIT消息測試
·讓步和休眠ui
·讓步的問題spa
·WaitMessage操作系統
16位MS-DOS/Windows環境和32位Win32?/Windows NT?環境有些很重要的不一樣之處。雖然這些不一樣之處在這兒沒法被忽視,但咱們仍是把它們作爲遺留問題,由之後的文章去解釋吧。翻譯
隊列對象
要理解GetMessage和PeekMessage的運做,必須首先明白Microsoft? Windows?操做系統是如何儲存事件和消息的。在Windows中有兩種類型的隊列爲此目的工做,它們分別是系統隊列和消息隊列。
硬件輸入:系統隊列
Windows有一些驅動程序,它們負責響應來自於鍵盤和鼠標等硬件的中斷服務。在中斷時間中,鍵盤和鼠標驅動程序會調用USER.EXE中指定的一些入口點去報告一個事件的發生。在Windows中服務於光筆計算的光筆驅動程序,一樣會在原始的光筆事件中調用這些入口點。
在Windows3.1中,系統隊列是一個有着120個入口空間的定長的隊列。在通常情形下這些「小房間」是足夠了,但若是應用程序掛起了或者在一段長的時間裏沒有及時處理任何消息就可能致使系統隊列被填滿。若是真的發生了,任未嘗試添加到系統隊列的新事件都將會引發系統蜂鳴。(譯者注:在DOS中,若是一個程序在一段時間內佔用了全部的系統資源,使機器沒法響應,這時若是你按住一個鍵不放,你就會聽到機箱喇叭嘀嘀做響)
發送的消息和程序隊列
當一個應用程序開始時,一個隊列將會所以而被建立。程序隊列(有時會稱爲任務隊列)經常用於儲存「正在被髮往應用程序的一個窗口」 的消息。惟一常駐程序隊列的消息是那些由PostMessage或PostAppMessage明確發送的消息。(SendMessage從不使用系統隊列)PostQuitMessage函數不會發送一個消息到程序隊列。(WM_QUIT消息將在下文中論討)
默認的,每一個程序隊列能夠保持八個消息。通常狀況下這是至關足夠的,由於PostMessage極少被使用。可是若是一個應用程序試圖強制調用不少的PostMessage到某個應用程序時,那麼這類應用程序將會用使用SetMessageQueue函數來增長消息隊列的長度。你必須當心的使用SetMessageQueue函數,由於它不管什麼時候都會先刪掉當前的程序隊列,並建立一個預期大小的新隊列,此時任何在舊隊列中的消息都會被銷燬。所以,它必須在你的WinMain例程中在全部其它的應用程序編程接口(API)以前調用或在應用程序用PeekMessage明確的清除隊列以後調用。
GetMessage和PeekMessage是怎樣工做的
在Windows的內部,GetMessage和PeekMessage執行着相同的代碼。而二者最大的不一樣之處則體如今沒有任何消息返回到應用程序的狀況下。在此種狀況下,PeekMessage會返回一個空值到應用程序,GetMessage會在此時讓應用程序休眠。在它們之間還有一些其它的不一樣,咱們將會在下面討論,但它們至關次要。
GetMessage和PeekMessage邏輯
下面一步步的講述了在Windows3.1版的GetMessage和PeekMessage公用代碼。
提示:下面所示步驟按照消息類型的優先權進行排序。舉個例子,發送的消息總在鍵盤和鼠標消息以前被返回,而鍵盤和鼠標的消息又會在繪圖(paint)消息以前反回,以此類推。
1. 檢視在爲「活動中任務」服務的程序隊列中是否有消息的存在。若是是,首先在隊首刪除此消息並將其返回到應用程序。而後,應用程序中的GetMessage和PeekMessage會調用一些代碼,用以從程序隊列中接收此消息,這些代碼是由該應用程序調用的動態連接庫(DLL)生成的。記住,只有由PostMessage發送的消息會常駐於此隊列中。
2. 與全部消息和窗體句柄過濾器進行對照,覈查此消息。若是此消息不匹配指定的過濾器,就會把此消息留在程序隊列中。若是隊列中在此消息的後面還有其它消息,則會轉向對下一個消息的處理。
3. 若是在程序隊列中沒有消息了,就掃描系統隊列中的事件。這個過程至關複雜,而且咱們將在下面的「掃描系統隊列」小節中XX。通常來說,在系統隊列首部的事件是供這個應用程序所使用的,系統會將其轉化爲消息,並將消息返回到這個應用程序中(它不會首先被置於應用隊列中)。注意,這個掃描系統隊列的過程可能致使當前活動的應用程序將控制權讓給其它的應用程序。
4. 若是在系統隊列中沒有等待處理的事件,則覈查全部與當前應用程序(任務)相關的窗體以肯定更新區域。當一個窗體的一部分須要被重繪時,一個更新區域就被建立在那個窗體部分之上。這個區域將與此窗體中現存的全部更新區域相結合,並儲存在內部窗體結構體中。若是GetMessage或PeekMessage在這個任務中發現某些窗體有一些未處理的更新區域,將產生一個WM_PAINT消息,併爲那個窗體返回到應用程序中。WM_PAINT從不駐留在任何隊列中。此時,一個應用程序將爲某個窗體不斷的接收WM_PATIN消息,直到更新區域由BeginPaint/EndPaint,ValidateRect,或ValidateRgn所清除。
5. 若是這個任務中沒有任何窗體須要被更新,GetMessage和PeekMessage就會在這一點讓出控制權,除非PeekMessage調用被設置爲PM_NOYIELD屬性。
6. 當讓步返回時,檢視在當前任務中是否有計時器到期。若是是,建立一個WM_TIMER消息並返回。它不但發生在「返回一個WM_TIMER消息到窗體」的計時器上,一樣也發生在「調用一個計時器處理過程」的計時器上。如要了解更多信息,請看在微軟開發者網絡(MSDN)光盤(包括技術文章、Windows文章、核心和驅動程序文章)中的文章「Timers and Timing in Microsoft Windows」(譯者注:若是讀者可以承認個人工做,我會竭盡全力地翻譯這篇關於計時器的文章)。
7. 若是這個應用程序沒有計時器事件服務,而且一個應用程序正在被終止,代碼將嘗試去縮小圖形設備界面(GDI)的本地內存堆。一些應用程序,好比繪圖應用程序(像Paintbrush?),爲GDI分配了大量的堆內存。當應用程序終止時釋放這些對象時,會使GDI本地內存堆被空閒空間填滿而膨脹。爲了恢復這些空閒的空間, 在GetMessage/PeekMessage處理中,LocalShrink將在這一點被調用於GDI的內存堆。這個被完成一次,(每次)一個應用程序將終止。
8. 在這一時刻,代碼將分叉爲兩條路,一是代碼任意的返回一個有效的消息,另外一個是徹底沒有這個應用程序去處理的消息、事件,而代碼最終會走哪條路決定於PeekMessage和GetMessage中的哪個被調用。
·PeekMessage. 若是PeekMessage被調用,並設置了PM_NOYIELD標記,PeekMessage在此刻返回一個空值,這個空返回值指出已經沒有要處理的消息了。若是沒有設置PM_NOYIELD標記,PeekMessage就在此刻讓出控制權。它不會休眠,但會單一的交給其它已準備好的應用程序一個執行的機會。(請參閱下面的「讓步與休眠的不一樣)當讓步返回,PeekMessage直接將控制權返回到應用程序,並返回一個空值,它指出這個應用程序沒有要處理的消息了。
?GetMessage. 在此刻,GetMessage會讓應用程序休眠、等待,直到一些事件發生須要喚醒應用程序。控制權不會返回到調用GetMessage的應用程序,直到有應用程序必須去處理的消息出現。一旦這個應用程序從被置入休眠狀態中醍來,GetMessage內部的循環將回到最開始(步驟1)。
WH_GETMESSAGE鉤子
在GetMessage和PeekMessage將一個消息返回到調用的應用程序以前,會作一個驗證是否存在一個WH_GETMESSAGE鉤子的測試。若是有一個已經被安裝了,那這個鉤子會被調用。若是PeekMessage沒有發現可用的消息並返回一個空值時,這個鉤子將不會被調用。在鉤子處理過程當中,你不可能得知是究竟是GetMessage被調用仍是PeekMessage被調用。
掃描系統隊列
綜上所述,在系統隊列中的事件僅僅是硬件事件的記錄。那些代碼掃描系統隊列的主要任務是,從這些事件中建立消息,並肯定哪個窗體將接收這個消息。
代碼第一次在系統隊列首部找到事件時,並不會立刻將其刪除。由於鼠標和鍵盤事件只是隊列中的兩種事件,而代碼會分枝(譯者注:相似於C語言中的switch語句)並單獨處理每一種類型的事件。