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

三、Window Procedure 與“Reentrant”及魚死網破
[四處蔓延、氾濫的重入性問題,魚死網破、漏網之魚]

其實就是在一個GUI線程內處理消息也是有點很不簡單的。先就看看窗口過程的重入性吧。

先看看Petzold的說法:
[《Programming Windows》第2版(1990年)的中譯版本:“”]


《Programming Windows》(1998年)第五版 第三章 Windows and Messages:“Although Windows programs can have multiple threads of execution, each thread's message queue handles messages for only the windows whose window procedures are executed in that thread. In other words, the message loop and the window procedure do not run concurrently. When a message loop retrieves a message from its message queue and calls DispatchMessage to send the message off to the window procedure, DispatchMessage does not return until the window procedure has returned control back to Windows.

However, the window procedure could call a function that sends the window procedure another message, in which case the window procedure must finish processing the second message before the function call returns, at which time the window procedure proceeds with the original message. For example, when a window procedure calls UpdateWindow, Windows calls the window procedure with a WM_PAINT message. When the window procedure finishes processing the WM_PAINT message, the UpdateWindow call will return controls back to the window procedure.

This means that window procedures must be reentrant. In most cases, this doesn't cause problems, but you should be aware of it. For example, suppose you set a static variable in the window procedure while processing a message and then you call a Windows function. Upon return from that function, can you be assured that the variable is still the same? Not necessarily—not if the particular Windows function you call generated another message and the window procedure changes the variable while processing that second message. This is one of the reasons why certain forms of compiler optimization must be turned off when compiling Windows programs.

In many cases, the window procedure must retain information it obtains in one message and use it while processing another message. This information must be saved in variables defined as static in the window procedure, or saved in global variables.
Of course, you'll get a much better feel for all of this in later chapters as the window procedures are expanded to process more messages. ”

Petzold認爲“window procedures must be reentrant”。請原諒我蹩腳的英語水平,我不知道他眼下之“In most cases”與“In many cases”竟能如此和諧、彼此兼容。【《Programming Windows》第2版(1990年)的中譯版本(《Windows編程》)中分別譯爲“多數情況下”與“在許多情況下”;《Programming Windows程序開發設計指南》第五版(2000.6),餘孟學先生分別譯爲“在大多數情況下”與“在許多情況下”;它們大致相同。】[相對比較與數目多少][小時候與大一點的小孩爭分東西時,大一點的要搶着先分,並貌似公平地說:“你要多半,我要大半——”,待他拿了大半之後實際只剩下小半了、哪裏還會有我的多半呢?!——那時儘管小也還是知道的,所以這一招也早就不靈了:等我先拿了多半,你再拿你的大半!我想現在這兒時的法子或許還可一用,那就是:先拿多半,即先“In many cases……”。]在許多情況下我們都將遭遇消息之間通過靜態或全局變量的通訊,要想做到窗口過程的重入性不得不謹慎從事,尤其當窗口過程被擴展來處理更多的消息時。

其實,就以MS推薦的GUI程序基本模型來說吧,如果把Windows GUI程序的主窗口/過程當作一張漁網頂部的網繩,那麼由此而帶出的大大小小的窗口/窗口過程就像網結一樣,佈滿了整張網,其中有它的許許多多的經線(綱),還有它的許許多多的緯線(目),要想在這張網上做到窗口過程的重入性,絕非易事(暫不考慮Incoming nonqueue messages對SendMessage函數的“拯救”機制所帶來的複雜性)!其中的危險性、複雜性、艱鉅性,MS也沒有“和盤托出”給我們,只是拿線程之間的SendMessage()來搪塞(卻不提引入SendMessage的拯救機制所帶來的後果)。而Petzold用近乎“大半”與“多半”的方式給我們一再強調線程內處理消息的難以避免、需要小心的“那些事兒”。

【局部可重入性、Weak Reentrant。路徑可重入性、條件可重入性、狀態可重入性。我們賴以苟延殘喘的東西。(相似物 :STL 之Weakly comparable,Weak Ordering)】

平常我們並沒有[常]碰到這種嚴重的重入性問題。一般情形下,我們只是做到了不那麼嚴格的局部重入性,但這也就是一般情形下較低的要求。什麼是局部重入性(或Weak Reentrant)?簡言之,就是,一個模塊在某種(執行)狀態下,這種狀態的條件、路線是不可重入性的,但其他的條件、路線是可重入性的,此中情形我們稱此模塊是局部可重入性(或Weak Reentrant)的。例如,平常的Windows GUI程序,我們由主窗口菜單進入About對話框,在About對話框顯示期間,主要窗口仍能刷新(因對話框內置消息循環並路由主窗口刷新消息之故),但不會再次進入主窗口過程的顯示About對話框的消息處理過程(部分)(有意或惡意下可以chk),這就是一種局部可重入性。Windows窗口系統對此是沒有保(證)障的,“”。要保障這點主要是程序員的責任。Modal對話框的天然屏障,即進入一個Modal對話框後不會再象之前那樣的方式進入這個Modal對話框(除非對話框自身引發、其他線程有意或惡意地引發),還有變灰禁用、層級隔離等局部化措施(狀態變量控制等),這是這些常規的局部化措施所實現的局部重入性,給了我們苟延殘喘的機會。當這些作爲保護網的天然屏障被有意或惡意踐踏之後,我們該如何【防範、】應對?網破之時魚將爲之奈何?是坐以待斃呢,還是未雨綢繆……

如果考慮到Incoming nonqueue messages對SendMessage函數的“拯救”機制的影響的話,其中的危險性、複雜性、艱鉅性將與日俱增,這種機制可以擾亂正常的消息序列(MS的說辭),而且由於MS對此也無有效的識別機制、選擇機制,也沒有有效的抵禦措施。MS不是提供了InSendMessage()函數來區分線程外與線程內發的消息麼?不過,Windows的窗口系統所發的消息中有的是以線程外Incoming nonque messages來發送的,有的又以線程內消息來發送的。如以Windows XP sp2爲例:

Windows系統所發消息   InSendMessage()測試結果
WM_ACTIVATE                   FALSE
WM_SETFOCUS                 FALSE
WM_SETCURSOR              FALSE


WM_GETICON                   TRUE
WM_SYNCPAINT               TRUE
WM_NCPAINT                    TRUE
WM_ERASEBKGND           TRUE
WM_CTLCOLORDLG         TRUE

[測試程序 略]
對此,MS並沒有相應的有效措施。

無論如何,對我們自己可控制其格式的消息、可控制【收】發各方的消息等可控制的消息,我們可以採取措施加以防範。對這些消息格式不在自己掌握之中、又不能阻止的消息,除了一一識別【】之外,我們也還沒有有效措施來識別哪是系統發的,哪是其他進程(中的線程)發的,哪是期望發送消息的線程發的,哪是惡意線程發的。即使在Windows NT系列操作系統下,由於Window對象不在Windows NT安全機制控制之下,這對本來就危險的機制無異於雪上加霜。一個以非管理員身份運行的進程可以無需安全檢查就能驅使管理員身份運行的GUI進程,而你想有效防範有時還難以奈何——似乎只好讓它一旁歇着了!【Windows GUI程序的脆弱性。】


四、標準的事件驅動模型與牛鼻子

發佈了48 篇原創文章 · 獲贊 7 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章