• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            bob gunderson

            msdn 技術(shù)組

            作于 :1992 11 11

            ?

            creamdog

            譯于: 2002 3 13

            ?

            譯者的話

            該文重點講述了 windows 處理事件、消息的具體過程和步驟。尤其是在系系處理鼠標鍵盤事件的過程上做了詳解。通過這篇文章,你將對 windows 的消息處理機制有一個較全面的了解。

            概念

            ?

            這篇文章解釋了 getmessage peekmessage 的內(nèi)部運作方式,同時也是一類與“消息及消息在 16 ms-dos?/microsoft? windows ?環(huán)境之下的影響”相關(guān)文章的基礎(chǔ)。我們將討論下面這些主題:

            ·系統(tǒng)和應(yīng)用程序隊列(譯者注:以下簡稱為“程序隊列”)

            · getmessage peekmessage 函數(shù)

            ·消息過濾

            · wm_quit 消息

            ·讓步和休眠

            ·讓步的問題

            · waitmessage

            ?

            16 ms-dos/windows 環(huán)境和 32 win32 ? /windows nt ?環(huán)境有些很重要的不同之處。雖然這些不同之處在這兒無法被忽視,但我們還是把它們做為遺留問題,由以后的文章去解釋吧。

            隊列

            要理解 getmessage peekmessage 的運作,必須首先明白 microsoft? windows ?操作系統(tǒng)是如何儲存事件和消息的。在 windows 中有兩種類型的隊列為此目的工作,它們分別是系統(tǒng)隊列和消息隊列。

            ?

            硬件輸入:系統(tǒng)隊列

            windows 有一些驅(qū)動程序,它們負責(zé)響應(yīng)來自于鍵盤和鼠標等硬件的中斷服務(wù)。在中斷時間中,鍵盤和鼠標驅(qū)動程序會調(diào)用 user.exe 中指定的一些入口點去報告一個事件的發(fā)生。在 windows 中服務(wù)于光筆計算的光筆驅(qū)動程序,同樣會在原始的光筆事件中調(diào)用這些入口點。

            windows3.1 中,系統(tǒng)隊列是一個有著 120 個入口空間的定長的隊列。在一般情形下這些“小房間”是足夠了,但如果應(yīng)用程序掛起了或者在一段長的時間里沒有及時處理任何消息就可能導(dǎo)致系統(tǒng)隊列被填滿。如果真的發(fā)生了,任何嘗試添加到系統(tǒng)隊列的新事件都將會引起系統(tǒng)蜂鳴。(譯者注:在 dos 中,如果一個程序在一段時間內(nèi)占用了所有的系統(tǒng)資源,使機器無法響應(yīng),這時如果你按住一個鍵不放,你就會聽到機箱喇叭嘀嘀作響)

            ?

            發(fā)送的消息和程序隊列

            當一個應(yīng)用程序開始時,一個隊列將會因此而被創(chuàng)建。程序隊列(有時會稱為任務(wù)隊列)常常用于儲存“正在被發(fā)往應(yīng)用程序的一個窗口” 的消息。唯一常駐程序隊列的消息是那些由 postmessage postappmessage 明確發(fā)送的消息。( sendmessage 從不使用系統(tǒng)隊列) postquitmessage 函數(shù)不會發(fā)送一個消息到程序隊列。( wm_quit 消息將在下文中論討)

            默認的,每個程序隊列可以保持八個消息。一般情況下這是相當足夠的,因為 postmessage 極少被使用。但是如果一個應(yīng)用程序試圖強制調(diào)用很多的 postmessage 到某個應(yīng)用程序時,那么這類應(yīng)用程序?qū)檬褂?/span> setmessagequeue 函數(shù)來增加消息隊列的長度。你必須小心的使用 setmessagequeue 函數(shù),因為它無論何時都會先刪掉當前的程序隊列,并創(chuàng)建一個預(yù)期大小的新隊列,此時任何在舊隊列中的消息都會被銷毀。因此,它必須在你的 winmain 例程中在所有其它的應(yīng)用程序編程接口( api )之前調(diào)用或在應(yīng)用程序用 peekmessage 明確的清除隊列之后調(diào)用。

            ?

            getmessage peekmessage 是怎樣工作的

            windows 的內(nèi)部, getmessage peekmessage 執(zhí)行著相同的代碼。而兩者最大的不同之處則體現(xiàn)在沒有任何消息返回到應(yīng)用程序的情況下。在此種情況下, peekmessage 會返回一個空值到應(yīng)用程序, getmessage 會在此時讓應(yīng)用程序休眠。在它們之間還有一些其它的不同,我們將會在下面討論,但它們相當次要。

            ?

            getmessage peekmessage 邏輯

            下面一步步的講述了在 windows3.1 版的 getmessage peekmessage 公用代碼。

            提示:下面所示步驟按照消息類型的優(yōu)先權(quán)進行排序。舉個例子,發(fā)送的消息總在鍵盤和鼠標消息之前被返回,而鍵盤和鼠標的消息又會在繪圖 (paint) 消息之前反回,以此類推。

            1. ???????? 檢視在為“活動中任務(wù)”服務(wù)的程序隊列中是否有消息的存在。如果是,首先在隊首刪除此消息并將其返回到應(yīng)用程序。然后,應(yīng)用程序中的 getmessage peekmessage 會調(diào)用一些代碼,用以從程序隊列中接收此消息,這些代碼是由該應(yīng)用程序調(diào)用的動態(tài)鏈接庫( dll )生成的。記住,只有由 postmessage 發(fā)送的消息會常駐于此隊列中。

            2. ???????? 與所有消息和窗體句柄過濾器進行對照,核查此消息。如果此消息不匹配指定的過濾器,就會把此消息留在程序隊列中。如果隊列中在此消息的后面還有其它消息,則會轉(zhuǎn)向?qū)ο乱粋€消息的處理。

            3. ???????? 如果在程序隊列中沒有消息了,就掃描系統(tǒng)隊列中的事件。這個過程相當復(fù)雜,并且我們將在下面的“掃描系統(tǒng)隊列”小節(jié)中 xx 。一般來講,在系統(tǒng)隊列首部的事件是供這個應(yīng)用程序所使用的,系統(tǒng)會將其轉(zhuǎn)化為消息,并將消息返回到這個應(yīng)用程序中(它不會首先被置于應(yīng)用隊列中)。注意,這個掃描系統(tǒng)隊列的過程可能導(dǎo)致當前活動的應(yīng)用程序?qū)⒖刂茩?quán)讓給其它的應(yīng)用程序。

            4. ???????? 如果在系統(tǒng)隊列中沒有等待處理的事件,則核查所有與當前應(yīng)用程序(任務(wù))相關(guān)的窗體以確定更新區(qū)域。當一個窗體的一部分需要被重繪時,一個更新區(qū)域就被創(chuàng)建在那個窗體部分之上。這個區(qū)域?qū)⑴c此窗體中現(xiàn)存的所有更新區(qū)域相結(jié)合,并儲存在內(nèi)部窗體結(jié)構(gòu)體中。如果 getmessage peekmessage 在這個任務(wù)中發(fā)現(xiàn)某些窗體有一些未處理的更新區(qū)域,將產(chǎn)生一個 wm_paint 消息,并為那個窗體返回到應(yīng)用程序中。 wm_paint 從不駐留在任何隊列中。此時,一個應(yīng)用程序?qū)槟硞€窗體不斷的接收 wm_patin 消息,直到更新區(qū)域由 beginpaint/endpaint validaterect ,或 validatergn 所清除。

            5. ???????? 如果這個任務(wù)中沒有任何窗體需要被更新, getmessage peekmessage 就會在這一點讓出控制權(quán),除非 peekmessage 調(diào)用被設(shè)置為 pm_noyield 屬性。

            6. ???????? 當讓步返回時,檢視在當前任務(wù)中是否有計時器到期。如果是,創(chuàng)建一個 wm_timer 消息并返回。它不但發(fā)生在“返回一個 wm_timer 消息到窗體”的計時器上,同樣也發(fā)生在“調(diào)用一個計時器處理過程”的計時器上。如要了解更多信息,請看在微軟開發(fā)者網(wǎng)絡(luò)( msdn )光盤(包括技術(shù)文章、 windows 文章、核心和驅(qū)動程序文章)中的文章“ timers and timing in microsoft windows ”(譯者注:如果讀者能夠認可我的工作,我會不遺余力地翻譯這篇關(guān)于計時器的文章)。

            7. ???????? 如果這個應(yīng)用程序沒有計時器事件服務(wù),并且一個應(yīng)用程序正在被終止,代碼將嘗試去縮小圖形設(shè)備界面( gdi )的本地內(nèi)存堆。一些應(yīng)用程序,比如繪圖應(yīng)用程序(像 paintbrush? ),為 gdi 分配了大量的堆內(nèi)存。當應(yīng)用程序終止時釋放這些對象時,會使 gdi 本地內(nèi)存堆被空閑空間填滿而膨脹。為了恢復(fù)這些空閑的空間, getmessage/peekmessage 處理中, localshrink 將在這一點被調(diào)用于 gdi 的內(nèi)存堆。這個被完成一次,(每次)一個應(yīng)用程序?qū)⒔K止。

            8. ???????? 在這一時刻,代碼將分叉為兩條路,一是代碼任意的返回一個有效的消息,另一個是完全沒有這個應(yīng)用程序去處理的消息、事件,而代碼最終會走哪條路決定于 peekmessage getmessage 中的哪一個被調(diào)用。

            · peekmessage. 如果 peekmessage 被調(diào)用,并設(shè)置了 pm_noyield 標記, peekmessage 在此刻返回一個空值,這個空返回值指出已經(jīng)沒有要處理的消息了。如果沒有設(shè)置 pm_noyield 標記, peekmessage 就在此刻讓出控制權(quán)。它不會休眠,但會單一的交給其它已準備好的應(yīng)用程序一個執(zhí)行的機會。(請參閱下面的“讓步與休眠的不同)當讓步返回, peekmessage 直接將控制權(quán)返回到應(yīng)用程序,并返回一個空值,它指出這個應(yīng)用程序沒有要處理的消息了。

            ? getmessage. 在此刻, getmessage 會讓應(yīng)用程序休眠、等待,直到一些事件發(fā)生需要喚醒應(yīng)用程序。控制權(quán)不會返回到調(diào)用 getmessage 的應(yīng)用程序,直到有應(yīng)用程序必須去處理的消息出現(xiàn)。一旦這個應(yīng)用程序從被置入休眠狀態(tài)中醍來, getmessage 內(nèi)部的循環(huán)將回到最開始(步驟 1 )。

            ?

            wh_getmessage 鉤子

            getmessage peekmessage 將一個消息返回到調(diào)用的應(yīng)用程序之前,會做一個驗證是否存在一個 wh_getmessage 鉤子的測試。如果有一個已經(jīng)被安裝了,那這個鉤子會被調(diào)用。如果 peekmessage 沒有發(fā)現(xiàn)可用的消息并返回一個空值時,這個鉤子將不會被調(diào)用。在鉤子處理過程中,你不可能得知是到底是 getmessage 被調(diào)用還是 peekmessage 被調(diào)用。

            ?

            掃描系統(tǒng)隊列

            綜上所述,在系統(tǒng)隊列中的事件僅僅是硬件事件的記錄。那些代碼掃描系統(tǒng)隊列的主要任務(wù)是,從這些事件中創(chuàng)建消息,并確定哪一個窗體將接收這個消息。

            代碼第一次在系統(tǒng)隊列首部找到事件時,并不會馬上將其刪除。因為鼠標和鍵盤事件只是隊列中的兩種事件,而代碼會分枝(譯者注:類似于 c 語言中的 switch 語句)并單獨處理每一種類型的事件。

            ?

            處理系統(tǒng)隊列中的鼠標事件

            下面是處理鼠標事件的步驟。

            1. ? 首先,將計算該事件屏幕坐標的相應(yīng)窗體。此計算(調(diào)用窗體點擊測試)以桌面窗體開始,從頭至尾的掃描細統(tǒng)中的每一個窗體(包括子窗體),直到找到一個包含這個鼠標坐標點的窗體,并且這個窗體沒有任何同樣包含這個坐標點的子窗體。

            2. 鼠標事件的窗體點擊測試

            例如:如果圖 2 中的箭頭代表當前的鼠標位置,任何的鼠標行為,像單擊鼠標鍵,將生成一個會在 b 窗體中產(chǎn)生消息的事件。

            2. ? 如果一個窗體使用 setcapture 捕獲鼠標,那么“系統(tǒng)隊列掃描”代碼將通過普通的點擊測試,并將所有的鼠標消息返回到捕獲的窗體。例如:如果在圖 2 中的 a 窗體調(diào)用了 setcapture ,則在箭頭所指位置的所有鼠標行為,將產(chǎn)生窗體 a 中的消息,而不是窗體 b

            3. ? 如果這個被處理的事件是一個“鼠標鍵按下”事件(任何一個鼠標鍵),代碼會檢測這個事件是否會轉(zhuǎn)化為雙擊事件。你可以在微軟開發(fā)者網(wǎng)絡(luò)(譯者注: msdn cd (技術(shù)文章, ask dr. gui )中的“ ask dr. gui #5 ”中找到關(guān)于雙擊轉(zhuǎn)化的描述。實質(zhì)上,如果在兩次鼠標鍵按下事件中,時間和距離的增量在允許的范圍之中,該事件將會生成一個雙擊消息,否則它將生成一個標準的“按下”事件。所有的鼠標事件都將生成標準的鼠標消息,而雙擊測試只在鼠標事件指定的,包含 cs_dblclks 類型的窗體中進行。

            4. ? 一個消息從鼠標事件中構(gòu)造出來。

            5. ? 如果鼠標點擊測試確定該事件發(fā)生在一個窗體的非客戶區(qū),如邊框或標題欄,那么該構(gòu)造出的消息映射到它相應(yīng)的非客戶區(qū)消息中。例如:一個 wm_mousemove 事件會被映謝為 wm_ncmousemove 消息。

            6. ? 與所有指定的消息過濾器進行對照,核查此消息。(請參閱下面的“消息范圍過濾和窗體句柄過濾”)如果該消息不匹配過濾器,則重新從頭開始“系統(tǒng)隊列掃描”代碼,查看隊列中的下一個消息。

            7. ? 如果鼠標消息需要前往與當前任務(wù)不同的另一個任務(wù)的相關(guān)窗體,事件會被留在系統(tǒng)隊列中,并且如果那個將會處理這個消息的任務(wù)在休眠之中,會被喚醒。這個新近被喚醒的任務(wù)不會在此刻立即運行,只會標記為準備運行。如果消息前往了其它任務(wù),并且在系統(tǒng)隊列中沒有要處理的事件被發(fā)現(xiàn),“系統(tǒng)隊列掃描”會代碼返回到 getmessage/peekmessage 主代碼。請參閱下面的“讓步與休眠的不同”以獲得更多的信息。

            8. ? 如果安裝了鼠標鉤子,它將在此刻被調(diào)用。如果鼠標鉤子返回了一個非零值,那么該鼠標事件被忽略,并從系統(tǒng)隊列中被刪除,然后重新從頭開始“系統(tǒng)隊列掃描”代碼。如果鉤子返回零,則繼續(xù)處理。

            9. ? 如果消息是一個“鼠標鍵按下”消息,“系統(tǒng)隊列掃描”則會在返回此消息之前,按照下面的方法激活窗體。

            ?它沿著父鏈一直向上尋找該窗體的“最終頂層父窗體”,直到相遇。

            ?它用 sendmessage 向該窗體的“最終頂層父窗體”發(fā)送一個 wm_mouseactivate 消息。

            ?從 wm_mouseactvate 返回的值將在下面被測試:

            a) ?????? 如果返回的值為空、 ma_activate 或者 ma_activateandeat activatewindow 函數(shù)將被調(diào)用去激活那個“最終頂層父窗體”。

            b) ????? 如果返回的值是 ma_noactivate 或者 ma_noactivateandeat ,窗體則不被激活。

            注意: ma_activateandeat ma_noactivateandeat 會導(dǎo)致“鼠標鍵按下”事件從系統(tǒng)隊列中被刪除,而不會生成一個鼠標按下消息。

            c) ????? 最終,一個 wm_setcursor 消息被發(fā)送到窗體,充許窗體設(shè)置指針的輪廓。

            10. 如果鼠標鉤子被調(diào)用,并且當前的鼠標事件從系統(tǒng)隊列中被刪除了,則檢查“基于計算機訓(xùn)練”( cbt )的鉤子。如果安裝有有一個 cbt 鉤子,將會攜帶 hcbt_clickskipped 鉤子碼代調(diào)用它。

            11. 按鍵狀態(tài)表包含了三個用于跟蹤鼠標按鍵狀態(tài)的入口。這些按鍵被分配予虛擬鍵代碼( vk_lbutton vk_rutton vc_mbutton ),它們和 getkeystate 一起始用去確事實上鼠標鍵是彈起還是按下。在返回鼠標消息之前,“系統(tǒng)隊列掃描”代碼會(為彈起或按下消息)設(shè)置按鍵狀態(tài)表并且從系統(tǒng)隊列中刪除消息。如果 peekmessage 被調(diào)用時攜帶 pm_noremove ,則按鍵狀態(tài)表不會被修改。

            ?

            處理系統(tǒng)隊列中的鍵盤事件

            1. ? 檢查是否 ctrl 鍵被按下和當前的事件是否 esc 鍵按。如果是,該用戶——直接窗體——會顯示任務(wù)管理器窗體。一個 wm_syscommand 消息將被發(fā)送到激活的窗體,并且參數(shù) wparam sc_tasklit 。然后鍵盤按下事件從系統(tǒng)隊列中被刪除,“系統(tǒng)隊列掃描”代碼又將重新從頭開始。如果此激活的窗體是一個系統(tǒng)模塊或者是一個被顯示出來的“硬”系統(tǒng)模塊消息框(比如一個“ int 24 小時系統(tǒng)錯誤消息框,或一個使用 mb_iconhand mb_systemmodal 參數(shù)的 messagebox 函數(shù))的事件,將會被拋棄。

            2. ? 下一步,試著去查看當前的事件是不是一個 print screen 鍵的按下事件。如果是,任意一個激活的窗體或整個桌面將被做為一個位圖快照,保存到剪貼板中。如果 alt 鍵被按下,一幅激活窗體的圖像被復(fù)制到剪貼板中;如果沒有,則是整個桌面被復(fù)制。然后 print screen 鍵按下事件從系統(tǒng)隊列中被刪除,“系統(tǒng)隊列掃描”代碼又將重新從頭開始。如果顯示了一個“硬”系統(tǒng)模塊消息框,則此操作被忽略。

            3. ? 下一步檢測熱鍵。使用程序管理器,用戶可以定義用來運行一個應(yīng)用程序的擊鍵事件。這些擊鍵被稱為熱鍵。如果當前的事件是一個按鍵事件,將會被測試是否與定義過的熱鍵匹配。如果發(fā)現(xiàn)匹配,一個 wm_syscommand 消息將被發(fā)送到激活的窗體,并且參數(shù) wparam sc_hotkey 。然后鍵盤按下事件從系統(tǒng)隊列中被刪除,“系統(tǒng)隊列掃描”代碼又將重新從頭開始。如果此激活的窗體是一個系統(tǒng)模塊或者是一個被顯示出來的“硬”系統(tǒng)模塊消息框,該測試被跳過。

            4. ? 一般情況下,所有的鍵盤消息(如 wm_keydown wm_char 等等)前往具有輸入焦點的窗體。如果這個具有輸入焦點的窗體與另一個當前執(zhí)行的任務(wù)相關(guān)聯(lián),那么該事件會被留在系統(tǒng)隊列中,并且那個擁有“有焦點的窗體”的任務(wù)會被喚醒(如果休眠了)。“系統(tǒng)隊列掃描”代碼會像沒要發(fā)現(xiàn)任何要處理的事件一樣,返回到主 getmessage/peekmessage 代碼。請參閱下面的“讓步與休眠的不同”和“應(yīng)用程序如何被喚醒”以獲得更多的信息。

            5. ? 如果遇到了沒有任何一個窗體具有輸入焦點的情形,鍵盤消息會直接前往當前激活的窗體,而不會被翻譯成為系統(tǒng)鍵消息(如 wm_syskeydow wm_syschar ,等等)。

            6. ? 與所有指定的消息過濾器進行對照,核查此消息。(請參閱下面的“消息范圍過濾和窗體句柄過濾”)如果該消息不匹配過濾器,則重新從頭開始“系統(tǒng)隊列掃描”代碼,查看隊列中的下一個消息。

            7. ? 如果事件被返到了當前的任務(wù),它將從系統(tǒng)隊列中被刪除掉,除非 peekmessage 被指定為 pm_noremove 標記。請參閱下面的“ peekmessage pm_noremove 標記”以了解更多的關(guān)于不從隊列中刪除事件的信息。

            8. ? 如果安裝有鍵盤鉤子,將在此刻被調(diào)用。如果事件從系統(tǒng)隊列中被刪除了,鉤子的調(diào)用將伴隨 hc_action 屬性;如果事件未被從系統(tǒng)隊列中刪除,鉤子的調(diào)用將具有 hc_norem 屬性。

            9. ? 如果鍵盤鉤子被調(diào)用,并且當前的按鍵事件從系統(tǒng)隊列中被刪除了,則檢查現(xiàn)存的 cbt 鉤子。如果安裝有 cbt 鉤子,將調(diào)用它并攜帶 hcbt_keyskipped 鉤子碼。

            10. ????????????? 最后,消息被返加到主 getmessage/peekmessage 代碼。

            ?

            peekmessage pm_noremove

            默認情況下,每一個消息被返回到應(yīng)用程序后, peekmessage ?getmessage 都會把消息和事件從系統(tǒng)隊列中刪除。然而有些時候,某個應(yīng)用程序可能需要掃描隊列中現(xiàn)存的消息而并不刪除它們。例如,某個應(yīng)用程序在做一些處理過程,這些處理過程期望“一但發(fā)現(xiàn)有可用的消息,就盡快終止”。

            Posted on 2006-09-14 18:18 艾凡赫 閱讀(1225) 評論(0)  編輯 收藏 引用 所屬分類: 基礎(chǔ)知識MFC技術(shù)win32 sdk 編程
            亚洲综合伊人久久综合| 国产午夜精品理论片久久| 99久久免费国产精品热| 亚洲AV无一区二区三区久久| 97精品国产97久久久久久免费| 久久99免费视频| 精品久久人人妻人人做精品| 久久国产精品成人免费| 99国产精品久久| 久久午夜福利无码1000合集| 久久亚洲国产成人影院网站| 日本久久久精品中文字幕| 久久久久18| 72种姿势欧美久久久久大黄蕉| 99久久精品国产综合一区| 精品久久久久成人码免费动漫| 国产午夜免费高清久久影院| 久久丫忘忧草产品| 欧美午夜A∨大片久久 | 无码国内精品久久人妻麻豆按摩 | 无码超乳爆乳中文字幕久久| 一本大道久久香蕉成人网| 一本一本久久aa综合精品| www.久久热.com| 国产激情久久久久久熟女老人| 99热成人精品免费久久| 亚洲精品综合久久| 国产精品久久久天天影视香蕉 | 亚洲欧洲日产国码无码久久99| 77777亚洲午夜久久多喷| 久久久久这里只有精品| 99久久国产主播综合精品| 精品久久久久久无码专区| 久久精品国产精品亚洲人人| 欧美久久综合九色综合| www久久久天天com| 久久精品国产亚洲AV无码偷窥| 久久中文骚妇内射| 久久精品国产99久久久古代| 久久国产乱子精品免费女| 综合人妻久久一区二区精品|