第五章 走得太遠別忘了回家的路(1) ——《箴言》第三章 Windows運行機理之讀書筆記


第五章 走得太遠,別忘了回家的路(1)
——《箴言》第三章 Windows運行機理之讀書筆記之一


【內核分析  運行機理 LE文件的格式 VxD的設計實現】(略)

一、“整個消息的處理就很簡單了”

“我們首先從16位的Windows來認識消息。在16位時代,Windows的整個內核是32位的、分時的、搶佔的。所以從Windows的內核模型得知,有兩種VM,一種是SYSTEM VM,一種是DOS的VM。一個系統中可以運行很多的DOS窗口,因爲在16位的時代,能運行DOS的程序很重要的,所以在當時,Windows的主要任務之一,就是能同時運行很多DOS窗口。Windows的內核實現上用了很多微內核,而微內核的工作很多是靠消息來完成的。”

樑先生雲:“在16位時代,Windows的整個內核是32位的、分時的、搶佔的。”我第一次聽到這樣說,別的就不說了,就看看那時成熟的Windows3.1吧,還是來看看MS自己的說法吧:關於16位還是32位:“Windows 3.1 and Windows 3.11 (called Windows for Workgroups) are the 16-bit version of the Windows operating system family.”(《Windows Architecture Training for Developers》1998 Microsoft Corporation) “Windows 3.1 is a 16-bit operating environment that runs on the MS-DOS operating system.”可見16位時代的Windows決不是“整個內核是32位”的;關於搶佔性:“There are two types of multitasking: cooperative (or nonpreemptive) and preemptive. In a cooperative multitasking environment such as Windows 3.1, use of the processor is never taken from a task. Instead, a task must voluntarily yield control of the processor before any other task can run.”,由此可見,16位時代也不是“Windows的整個內核是搶佔的”,而是非搶佔性的(nonpreemptive)。非搶佔性的而想要同時又是“分時的”,孤陋寡聞的我又算是開了一回眼。

至於說到16位時代的Windows要能運行DOS程序,我想,如果16位時代的Windows這種座落在MS-DOS基礎上的操作環境封殺了MS-DOS程序,那纔是天大的笑話呢。16位時代的“Windows的內核實現上用了很多微內核”?如果不是作者把VM與微內核混爲一談的話,那麼就又是我的孤陋寡聞了。


“到Windows 32位時,消息的運行機理就不相同了。從內核中可以看出,有一個Win32的VxD,把DOS的搶佔分時都放在這個VM中完成,系統VM就進一步和系統底層融合。然後在這個基礎上分出時間片。這樣,每個應用程序就自己有自己的消息隊列。所有的消息隊列看上去是放在USER32的模塊內,但每個應用程序自己有一個USER32,因爲每個應用程序在內存內都是從4000000B(也就是4MB的位置開始的),這樣,每個GetMessage和PeekMessage都在處理事件。實際上,每個GetMessage都會成爲一個WaitsingleMessage,當有事件來後,就直接進行處理,也不用做什麼調度。因爲自己完成自己的消息處理,每個程序都是獨立的,所以要用底層內核來實現頁面的切換。它某程序切入時,其他程序就會被切出。當切換出去時,整個消息隊列也就被切換出去了。所以,整個消息的處理就很簡單了。”

從樑先生對Windows 32位的描述(內核中的“Win32的VxD”、DOS VM、“系統VM”)來看,那就以Windows 95爲例吧。爲了敘述用語與作者保持一致,下面我們姑且把“每個應用程序”當作每個進程來看。

“每個應用程序就自己有自己的消息隊列”?每個應用程序?且不說MS-DOS程序了,就說16位和32位的Windows-based應用程序吧。關於Win32線程,MSDN2001(PSDK - Message Routing - Queued Messages)上說,“The system maintains a single system message queue and one thread-specific message queue for each GUI thread. To avoid the overhead of creating a message queue for non-GUI threads, all threads are created initially without a message queue. The system creates a thread-specific message queue only when the thread makes its first call to one of the User or GDI functions.”Win32之非GUI線程就沒有線程(登記)消息隊列(與Win16不同)。關於系統消息隊列,MSDN2001中Windows 95 Resources Kit(第31章)說:“Windows 95 uses an asynchronous input model for all input to the system and applications. As the various input devices generate interrupts, the interrupt handler converts these interrupts to messages and sends the messages to a raw input thread area, which in turn passes each message to the appropriate message queue. Although each Win32-based thread can have its own message queue, all Win16-based applications share a common message queue”([system message queue] NT3.51Arch User介紹中談及Win16 “a Common Queue”--RIQ)。爲了最大限度地提供兼容性,仿真了16位Windows的Windows-based應用程序運行環境,16位的Windows-based應用程序在Windows 95中都共享一個系統消息隊列(Windows NT對此處理不同)。

“Windows 32位”下16位的Windows-based應用程序同樣也是非搶佔性的,正是需要調度的(參見MSDN2001 技術文章:《GetMessage and
PeekMessage Internals》Bob Gunderson 1992年)。因此也就不是“每個GetMessage都會成爲一個WaitsingleMessage,當有事件來後,就直接進行處理,也不用做什麼調度。因爲自己完成自己的消息處理,每個程序都是獨立的”。至於32位的Windows-based應用程序,也不是“每個GetMessage都會成爲一個WaitsingleMessage,當有事件來後,就直接進行處理,也不用做什麼調度”的。當有Posted事件來後,此時如果也已有其他應用程序發送的消息,那麼就需要調度,按照Windows的處理規則,這樣的發送消息將先得到處理,然後纔可能是Posted消息。

32位的Windows-based應用程序,不僅GUI程序(進程)這樣的“每個應用程序就自己有自己的消息隊列”,而且更具體地說每個GUI線程都有自己的消息隊列,同一進程中的線程收到其他GUI線程發送的消息時也象其他進程(中的線程)發送的消息那樣需要調度的處理。


在列舉了MSG結構之後,“從以上結構中可以看到,每個消息都對應着一個窗口。USER模塊是管理窗口的,一般每個窗口自己有一個消息隊列,當鍵盤或鼠標有消息時,就會發給激活的窗口,當在程序設計中用SendMessage來發送消息時,就會明確指定窗口句柄,當運行此函數後,就會把消息放到此窗口的消息隊列中。”

“從以上結構(MSG結構 筆者注)中可以看到,每個消息都對應着一個窗口”,不錯,每個消息(結構)都有一個窗口句柄(成員)變量,但卻未必總“都對應着一個窗口”。直接用PostMessage(NULL, message, wparam, lparam)來Posted至線程的消息,雖然也要指定這個窗口句柄值,但那是一個NULL值,意味着這個消息不“對應着一個窗口”。又如用Win32的PostThreadMessage() 或Win16的PostAppMessage()Posted的消息,那消息結構中的窗口句柄也不“對應着一個窗口”。而對PostMessage(HWND_TOPMOST/HWND_BROADCAST, message, wparam, lparam)來說,則是廣播消息,則此時一個消息就常要對應多個窗口。

樑先生雲“一般每個窗口自己有一個消息隊列”。“每個窗口自己”?無論是16位的Windows還是32位的Windows(Windows 9x及Windows NT系列)都沒有達到這個地步。即使是32位的Windows應用程序(進程)也只是每個GUI線程也只有一個消息隊列而已(從應用程序的角度上)。【(Posted)消息隊列歸屬Task(APP, Win16)或Thread(GUI,Win32)】

:“當在程序設計中用SendMessage來發送消息時,就會明確指定窗口句柄,當運行此函數後,就會把消息放到此窗口的消息隊列中。”線程內用SendMessage來發送消息時,就像普通的函數調用一樣,是直接進行的,沒有經過消息隊列這一環節。關於線程內發送消息的情形,我們還是看看隨後作者自己的話吧:“SendMessage:當用它向一個窗口(也可以是本身窗口)發送消息時,它不會把消息放入消息隊列中,而是直接發送給窗口。窗口接到消息後就立即處理,處理完成後,把結果作爲返回值傳送回來。這樣的處理過程就像是操作函數一樣。”即使是其他線程
用SendMessage來發送消息時也不一律“就會把消息放到此窗口的消息隊列中”,在Win16中並不把消息放在窗口的消息隊列中(參見MSDN2001 技術文章:《GetMessage and PeekMessage Internals》Bob Gunderson 1992年);【在不能立即調度執行的情況下,在Windows NT 3.1中,據Jeffrey的看法,纔是這樣的(這點與MSDN2001有出入);】而按照MSDN及《Programming Applications for Microsoft Windows》的說法,在不能立即調度執行的情況下,Windows NT 系列(NT xx以後?)則消息並不放在登記消息隊列中、而是放在發送消息隊列中(MSDN只稱說“pending messages”)(可以參看《Programming Applications for Microsoft Windows》1999年第4版)。


“其實,明白了消息的處理過程,消息也就很簡單了。消息不過是定義一個結構,定義一堆ID,在程序運行中調用switch和case去完成相應的功能。” switch和case這樣的處理方式只是其中的一種形式而已,ATL/WTL的if-else結構也是一種形式,MFC的映射表也是一種形式,等等。


線程外用SendMessage發來的消息並不就總是“直接發送給窗口的”,它是需要調度的,如果接受消息的線程尚未將控制權交給系統【SendMessage之Incomming nonqueue message缺口】,那麼系統此時是不能將此消息“直接發送給窗口”的,否則此線程的消息處理或將會亂成一鍋粥了。至於控制權,對Windows事件驅動系統而言,大抵是由應用程序通過調用GetMessage、PeekMessage等來控制的,這些調用可以說是Windows事件驅動系統之控制權管理的“原語”。【我們來看看MSDN(2001.10)上的說法:SendMessage:“If the specified window was
created by a different thread, the system switches to that thread and calls the appropriate window procedure. Messages sent between threads are processed only when the receiving thread executes message retrieval code.”(適用於Windows NT 3.1 and later, Windows 95 and later)】


“PostMessage:當用它向一個窗口(也可以是本身窗口)發送消息時,它把消息放入消息隊列中,自己什麼也不幹就會返回,到底消息什麼時候處理,有沒有被處理它是不知道的。”

關於PostMessage,有一種情形,就是不一定總是“當用它向一個窗口(也可以是本身窗口)發送消息時,它把消息放入消息隊列中”。“A common programming error is to assume that the PostMessage function always posts a message. This is not true when the message queue is full. An application should check the return value of the PostMessage function to determine whether the message has been posted and, if it has not been, repost it.”(Platform SDK: Windows User Interface  MSDN2001.10)【與MAC處理方式不同(見《Inside Macintosh》1985年),下面在適當時候再來看看】


“其實,很多程序可以完全不用註冊窗口。它只要做一些事件,當有事件來時,就處理相應的事件。例如,以下就是一個Windows程序,其中就沒有用到任何消息循環,只是在運行中彈出一個對話框:
//---------------------------------------------------------------
    //  HelloMsg.c – Displays “Hello Windows 98!” in a
//message box
//  -----------------------------------------------------------
    
#include <windows.h>
    
int WINAPI WinMain (  HINSTANCE hInstance,
                         HINSTANCE hPrevInstance,
                         PSTR szCmdLine, int iCmdShow)
    {
        MessageBox (NULL, TEXT (“Hello, Windows 98!”),  TEXT (“HelloMsg”), 0) ;
       return 0 ;
    }
可以看到,這個Win32的程序就沒用到微軟的框架,它完全是自己做自己的事,直到被中止。”

【“很多程序可以完全不用註冊窗口”,因爲MessageBox所建立的窗口是一種system classes,“#32770”(The class for a dialog box.)
。“A system class is a window class registered by the system. Many system classes are available for all processes to use, while others are used only internally by the system.”[Message Windows 2000/XP: The class for a message-only window.]
(MSDN2001.10)】

其實,MessageBox就已內置了消息循環。“An application creates the message box by using the MessageBox or MessageBoxEx function
”,“A message box is a modal dialog box and the system creates it by using the same internal functions that DialogBox uses.”關於DialogBox,“The function displays the dialog box (regardless of whether the template specifies the WS_VISIBLE style), disables the owner window, and starts its own message loop to retrieve and dispatch messages for the dialog box.” (Platform SDK: Windows User Interface  MSDN2001.10)如果我們的高手對MFC不是那麼不屑一顧的話,還可在MFC庫的源代碼中看到MFC通過建立Windows無模式對話框來實現MFC模式對話框的運行機理,其中就包含了內部消息循環的建立,這對DialogBox、MessageBox之類的內部消息循環處理的理解不無裨益罷。


至此“消息的運行方式”、“Windows的消息內核原理”、“Windows系統中消息的運作方式”等等,就算是結束了。無怪乎樑先生雲“整個消息的處理就很簡單了”,“其實,明白了消息的處理過程,消息也就很簡單了”。高手舉重若輕的本領也象以前一樣不得不令人佩服。在領略了好些“你不說我還明白,你越說我越糊塗了”的所謂“簡單”之後,不過還是不解心中的諸多疑惑……


二、“Message Deadlocks”、大師Petzold與Jeffrey的差異

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章