Windows核心编程 第27章 硬件输入模型和局部输入状态
第27章?硬件輸入模型和局部輸入狀態(tài)
? ? 這章說的是按鍵和鼠標(biāo)事件是如何進(jìn)入系統(tǒng)并發(fā)送給適當(dāng)?shù)拇翱谶^程的。微軟設(shè)計輸入模型的一個主要目標(biāo)就是為了保證一個線程的動作不要對其他線程的動作產(chǎn)生不好的影響。
27.1?原始輸入線程
? ? 當(dāng)系統(tǒng)初始化時,要建立一個特殊的線程,即原始輸入線程(raw?input?thread,R?I?T)。此外,系統(tǒng)還要建立一個隊列,稱為系統(tǒng)硬件輸入隊列(System?hardware?input?queue,?SHIQ)。R?I?T和S?H?I?Q構(gòu)成系統(tǒng)硬件輸入模型的核心。
?
? ? R?I?T怎么才能知道要向哪一個線程的虛擬輸入隊列里增加硬件輸入消息?對鼠標(biāo)消息,R?I?T只是確定是哪一個窗口在鼠標(biāo)光標(biāo)之下。利用這個窗口,?R?I?T調(diào)用G?e?t?Wi?n?d?o?w?T?h?r?e?a?dP?r?o?c?e?s?s?I?d來確定是哪個線程建立了這個窗口。返回的線程?I?D指出哪一個線程應(yīng)該得到這個鼠標(biāo)消息。
對按鍵硬件事件的處理稍有不同。在任何給定的時刻,只有一個線程同?R?I?T“連接”。這個線程稱為前景線程(foreground?thread),因為它建立了正在與用戶交互的窗口,并且這個線程的窗口相對于其他線程所建立的窗口來說處在畫面中的前景。
? ? 當(dāng)一個用戶在系統(tǒng)上登錄時,?Windows?Explorer進(jìn)程讓一個線程建立相應(yīng)的任務(wù)欄(t?a?s?k?b?a?r)和桌面。這個線程連接到R?I?T。如果你又要產(chǎn)生C?a?l?c?u?l?a?t?o?r,那么就又有一個線程來建立一個窗口,并且這個線程變成連接到?R?I?T的線程。注意現(xiàn)在Windows?Explorer的線程不再與R?I?T連接,因為在一個時刻只能有一個線程同R?I?T連接。當(dāng)一個按鍵消息進(jìn)入S?H?I?Q時,R?I?T就被喚醒,將這個事件轉(zhuǎn)換成適當(dāng)?shù)陌存I消息,并將消息放入與R?I?T連接的線程的虛擬輸入隊列。
? ? 不同的線程是如何連接到R?I?T的呢?我們已經(jīng)說過,當(dāng)產(chǎn)生一個進(jìn)程時,這個進(jìn)程的線程可以建立一個窗口。這個窗口處于前景,其建立窗口的線程同?R?I?T相連接。另外,R?I?T還要負(fù)責(zé)處理特殊的鍵組合,如A?l?t?+?Ta?b、A?l?t?+?E?s?c和C?t?r?l?+?A?l?t?+?D?e?l等。因為R?I?T在內(nèi)部處理這些鍵組合,就可以保證用戶總能用鍵盤激活窗口。應(yīng)用程序不能夠攔截和廢棄這些鍵組合。當(dāng)用戶按動了某個特殊的鍵組合時,R?I?T激活選定的窗口,并將窗口的線程連接到R?I?T。Wi?n?d?o?w?s也提供激活窗口的功能,使窗口的線程連接到R?I?T。
? ? 從上面的圖中可以看到如何保護(hù)線程,避免相互影響的。如果?R?I?T向窗口?B?1?或窗口?B?2?發(fā)送一個消息,消息到達(dá)線程?B的虛擬輸入隊列。在處理消息時,線程?B在與五個內(nèi)核對象同步時可能會進(jìn)入死循環(huán)或死鎖。如果發(fā)生這種情況,線程仍然同?R?I?T連接在一起,并且可能有更多的消息要增加到線程的虛擬輸入隊列中。
這種情況下,用戶會發(fā)現(xiàn)窗口?B?1和B?2都沒有反應(yīng),可能想切換到窗口?A?1?。為了做這種切換,用戶按A?l?t?+?Ta?b。因為是R?I?T處理A?l?t?+?Ta?b按鍵組合,所以用戶總能切換到另外的窗口,不會有什么問題。在選定窗口?A?1?之后,線程?A就連接到R?I?T。這個時候,用戶就可以對窗口?A?1?進(jìn)入輸入,盡管線程及其窗口都沒有響應(yīng)。
27.2?局部輸入狀態(tài)
??哪一個窗口有鼠標(biāo)捕獲。
??鼠標(biāo)光標(biāo)的形狀。
??鼠標(biāo)光標(biāo)的可見性。
????由于每個線程都有自己的輸入狀態(tài)變量,每個線程都有不同的焦點窗口、鼠標(biāo)捕獲窗口等概念。從一個線程的角度來看,或者它的某個窗口擁有鍵盤焦點,或者系統(tǒng)中沒有窗口擁有鍵盤焦點;或者它的某個窗口擁有鼠標(biāo)捕獲,或者系統(tǒng)中沒有窗口擁有鼠標(biāo)捕獲,等等。
27.2.1?鍵盤輸入與焦點
???R?I?T使用戶的鍵盤輸入流向一個線程的虛擬輸入隊列,而不是流向一個窗口。R?I?T將鍵盤事件放入線程的虛擬輸入隊列時不用涉及具體的窗口。當(dāng)這個線程調(diào)用G?e?t?M?e?s?s?a?g?e時,鍵盤事件從隊列中移出并分派給當(dāng)前有輸入焦點的窗口。(由該線程所建立)。下圖說明了這個處理過程。
? ? 線程1當(dāng)前正在從R?I?T接收輸入,用窗口A、窗口B或窗口C的句柄作參數(shù)調(diào)用S?e?t?F?o?c?u?s會引起焦點改變。失去焦點的窗口除去它的焦點矩形或隱藏它的插入符號,獲得焦點的窗口畫出焦點矩形或顯示它的插入符號。
? ? 假定線程1仍然從R?I?T接收輸入,并用窗口?E的句柄作為參數(shù)調(diào)用?S?e?t?F?o?c?u?s。這種情況下,系統(tǒng)阻止執(zhí)行這個調(diào)用,因為想要設(shè)置焦點的窗口不使用當(dāng)前連接?R?I?T的虛擬輸入隊列。在線的線程不一樣,那么,對于建立失去焦點窗口的線程,要更新它的局部輸入狀態(tài)變量,說明它沒有窗口擁有焦點。這時調(diào)用G?e?t?F?o?c?u?s將返回N?U?L?L,這會使線程知道當(dāng)前沒有窗口擁有焦點。
????函數(shù)S?e?t?A?c?t?i?v?e?Wi?n?d?o?w激活系統(tǒng)中一個最高層(?t?o?p?-?l?e?v?e?l)的窗口,并對這個窗口設(shè)定焦點:
HWND?WINAPI?SetActiveWindow(__in?HWND?hWnd);
同S?e?t?F?o?c?u?s函數(shù)一樣,如果調(diào)用線程沒有創(chuàng)建作為函數(shù)參數(shù)的窗口,則這個函數(shù)什么也不做。
與S?e?t?A?c?t?i?v?e?Wi?n?d?o?w相配合的函數(shù)是G?e?t?A?c?t?i?v?e?Wi?n?d?o?w函數(shù):
HANDLE?GetActiveWindow();
? ? 這個函數(shù)的功能同G?e?t?F?o?c?u?s函數(shù)差不多,不同之處是它返回由調(diào)用線程的局部輸入狀態(tài)變量所指出的活動窗口的句柄。當(dāng)活動窗口屬于另外的線程時,?G?e?t?A?c?t?i?v?e?Wi?n?d?o?w返回N?U?L?L。
? ? 其他可以改變窗口的?Z序(Z?-?o?r?d?e?r)、活動狀態(tài)和焦點狀態(tài)的函數(shù)還包括?B?r?i?n?g?Wi?n?d?o?w?ToTo?p和S?e?t?Wi?n?d?o?w?P?o?s:
?
BOOL?WINAPI?BringWindowToTop(__in?HWND?hWnd);
?
BOOL?WINAPI?SetWindowPos(
????_In_?HWND?hWnd,
????_In_opt_?HWND?hWndInsertAfter,
????_In_?int?X,
????_In_?int?Y,
????_In_?int?cx,
????_In_?int?cy,
????_In_?UINT?uFlags);
? ? 這兩個函數(shù)功能相同(實際上,?B?r?i?n?g?Wi?n?d?o?w?To?To?p函數(shù)在內(nèi)部調(diào)用?S?e?t?Wi?n?d?o?w?P?o?s,以H?W?N?D?_?TO?P作為第二個參數(shù))。如果調(diào)用這兩個函數(shù)的線程沒有連接到?R?I?T,則函數(shù)什么也不做。如果調(diào)用這些函數(shù)的線程同?R?I?T相連接,系統(tǒng)就會激活相應(yīng)的窗口。注意即使調(diào)用線程不是建立這個窗口的線程,也同樣有效。這意味著,這個窗口變成活動的,并且建立這個窗口的線程被連接到R?I?T。這也引起調(diào)用線程和新連接到R?I?T的線程的局部輸入狀態(tài)變量被更新。
? ? 有時候,一個線程想讓它的窗口成為屏幕的前景。例如,有可能會利用?Microsoft?Qutlook
安排一個會議。在會議開始前的半小時,?O?u?t?l?o?o?k彈出一個對話框提醒用戶會議將要開始。如果Q?u?t?l?o?o?k的線程沒有連接到R?I?T,這個對話框就會藏在其他窗口的后面,有可能看不見它。
? ? 為了制止這種現(xiàn)象,微軟對?S?e?t?F?o?r?e?g?r?o?u?n?d?Wi?n?d?o?w函數(shù)增加了更多的智能。特別規(guī)定,僅當(dāng)調(diào)用一個函數(shù)的線程已經(jīng)連接到?R?I?T或者當(dāng)前與R?I?T相連接的線程在一定的時間內(nèi)(這個時間量由S?y?s?t?e?m?P?a?r?a?m?e?t?e?r?s?I?n?f?o函數(shù)和S?P?I?_?S?E?T?F?O?R?E?G?R?O?U?N?D?_?L?O?C?K?T?I?M?E?O?U?T值來控制)沒有收到任何輸入,這個函數(shù)才有效。另外,如果有一個菜單是活動的,這個函數(shù)就失效。
? ? 如果不允許S?e?t?F?o?r?e?g?r?o?u?n?d?Wi?n?d?o?w將窗口移到前景,它會閃爍該窗口的標(biāo)題欄和任務(wù)條上該窗口的按鈕。用戶看到任務(wù)條按鈕閃爍,就知道該窗口想得到用戶的注意。用戶應(yīng)該手工激活這個窗口,看一看要報告什么信息。還可以用S?y?s?t?e?m?P?a?r?a?m?e?t?e?r?s?I?n?f?o函數(shù)和S?P?I?_?S?E?T?F?O?R?E?G?R?O?U?N?D?-F?L?A?S?H?C?O?U?N?T值來控制閃爍。
? ? 由于這些新的內(nèi)容,系統(tǒng)又提供了另外一些函數(shù)。如果調(diào)用?A?l?l?o?w?S?e?t?F?o?r?e?g?r?o?u?n?d?Wi?n?d?o?w的線程能夠成功調(diào)用S?e?t?F?o?r?e?g?r?o?u?n?d?Wi?n?d?o?w,第一個函數(shù)(見下面所列)可使指定進(jìn)程的一個線程成功調(diào) ? ?用S?e?t?F?o?r?e?g?r?o?u?n?d?Wi?n?d?o?w。為了使任何進(jìn)程都可以在你的線程的窗口上彈出一個窗口,指定A?S?F?W?_?A?N?Y?(定義為-1?)作為d?w?P?r?o?c?e?s?s?I?d參數(shù):
? ? 此外,線程可以鎖定?S?e?t?F?o?r?e?g?r?o?u?n?d?Wi?n?d?o?w函數(shù),使它總是失效的。方法是調(diào)用?L?o?c?kS?e?t?F?o?r?e?g?r?o?u?n?d?Wi?n?d?o?w。
BOOL?LockSetForegroundWindow(UINT?uLockCode);
? ? 對u?L?o?c?k?C?o?d?e參數(shù)可以指定L?S?F?W?_?L?O?C?K或者L?S?F?W?_?U?N?L?O?C?K。當(dāng)一個菜單被激活時,系統(tǒng)在內(nèi)部調(diào)用這個函數(shù),這樣一個試圖跳到前景的窗口就不能關(guān)閉這個菜單。?Wi?n?d?o?w?sE?x?p?l?o?r?e?r在顯示S?t?a?r?t菜單時,需要明確地調(diào)用這些函數(shù),因為?S?t?a?r?t菜單不是一個內(nèi)置菜單。當(dāng)用戶按了A?l?t鍵或者將一個窗口拉到前景時,系統(tǒng)自動解鎖?S?e?t?F?o?r?e?g?r?o?u?n?d?Wi?n?d?o?w函數(shù)。這可以防止一個程序一直對S?e?t?F?o?r?e?g?r?o?u?n?d?Wi?n?d?o?w函數(shù)封鎖。
? ? 關(guān)于鍵盤管理和局部輸入狀態(tài),其他的內(nèi)容是同步鍵狀態(tài)數(shù)組。每個線程的局部輸入狀態(tài)變量都包含一個同步鍵狀態(tài)數(shù)組,但所有的線程要共享一個同步鍵狀態(tài)數(shù)組。這些數(shù)組反映了在任何給定時刻鍵盤所有鍵的狀態(tài)。利用?G?e?t?A?s?y?n?c?K?e?y?S?t?a?t?e函數(shù)可以確定用戶當(dāng)前是否按下了鍵盤上的一個鍵:
SHORT?WINAPI?GetAsyncKeyState(__in?int?vKey);
? ? 參數(shù)n?Vi?r?t?K?e?y指出要檢查鍵的虛鍵代碼。結(jié)果的高位指出該鍵當(dāng)前是否被按下(是為?1,否為0)。筆者在處理一個消息時,常用這個函數(shù)來檢查用戶是否釋放了鼠標(biāo)主按鈕。為函數(shù)參數(shù)賦一個虛鍵值V?K?_?L?B?U?T?TO?N,并等待返回值的高位成為0。注意,如果調(diào)用函數(shù)的線程不是建立的窗口上,鼠標(biāo)光標(biāo)就可見了。
鼠標(biāo)光標(biāo)管理的另一個方面是使用C?l?i?p?C?u?r?s?o?r函數(shù)將鼠標(biāo)光標(biāo)剪貼到一個矩形區(qū)域。
BOOL?ClipCursor(CONST?RECT?*prc);
? ? 這個函數(shù)使鼠標(biāo)被限制在一個由p?r?c參數(shù)指定的矩形區(qū)域內(nèi)。當(dāng)一個程序調(diào)用?C?l?i?p?C?u?r?s?o?r函數(shù)時,系統(tǒng)該做些什么呢?允許剪貼鼠標(biāo)光標(biāo)可能會對其他線程產(chǎn)生不利影響,而不允許剪貼鼠標(biāo)光標(biāo)又會影響調(diào)用線程。微軟實現(xiàn)了一種折衷的方案。當(dāng)一個線程調(diào)用這個函數(shù)時,系統(tǒng)將鼠標(biāo)光標(biāo)剪貼到指定的矩形區(qū)域。但是,如果同步激活事件發(fā)生(當(dāng)用戶點擊了其他程序的窗口,調(diào)用了S?e?t?F?o?r?e?g?r?o?u?n?d?Wi?n?d?o?w,或按了C?t?r?l?+?E?s?c組合鍵),系統(tǒng)停止剪貼鼠標(biāo)光標(biāo)的移動,允許鼠標(biāo)光標(biāo)在整個屏幕上自由移動。
27.3?將虛擬輸入隊列同局部輸入狀態(tài)掛接在一起
?????從上面的討論我們可以看出這個輸入模型是強壯的,因為每個線程都有自己的局部輸入狀態(tài)環(huán)境,并且在必要時每個線程可以連接到?R?I?T或從R?I?T斷開。有時候,我們可能想讓兩個或多個線程共享一組局部輸入狀態(tài)變量及一個虛擬輸入隊列。
可以利用A?t?t?a?c?h?T?h?r?e?a?d?I?n?p?u?t函數(shù)來強制兩個或多個線程共享同一個虛擬輸入隊列和一組局部輸入狀態(tài)變量:
BOOL?WINAPI?AttachThreadInput(
????__in?DWORD?idAttach,
__in?DWORD?idAttachT);
? ? 函數(shù)的第一個參數(shù)i?d?A?t?t?a?c?h,是一個線程的I?D,該線程所包含的虛擬輸入隊列(以及局部輸入狀態(tài)變量)是你不想再使用的。第二個參數(shù)?i?d?A?t?t?a?c?h?To,是另一個線程的I?D,這個線程所包含的虛擬輸入隊列(和局部輸入狀態(tài)變量)是想讓兩個線程共享的。第三個參數(shù)?f?A?t?t?a?c?h,當(dāng)想讓共享發(fā)生時,被設(shè)置為?T?R?U?E,當(dāng)想把兩個線程的虛擬輸入隊列和局部輸入狀態(tài)變量分開時,設(shè)定為FA?L?S?E。可以通過多次調(diào)用A?t?t?a?c?h?T?h?r?e?a?d?I?n?p?u?t函數(shù)讓多個線程共享同一個虛擬輸入隊列和局部輸入狀態(tài)變量。
? ? 我們再考慮前面的例子,假定線程?A調(diào)用A?t?t?a?c?h?T?h?r?e?a?d?I?n?p?u?t,傳遞線程?A的I?D作為第一個參數(shù),線程B的I?D作為第二個參數(shù),T?R?U?E作為最后一個參數(shù):
線程?A的虛擬輸入隊列將不再接收輸入事件,除非再一次調(diào)用A?t?t?a?c?h?T?h?r?e?a?d?I?n?p?u?t并傳遞FA?L?S?E作為最后一個參數(shù),將兩個線程的輸入隊列分開。
? ? 當(dāng)將兩個線程的輸入都掛接在一起時,就使線程共享單一的虛擬輸入隊列和同一組局部輸入狀態(tài)變量。但線程仍然使用自己的登記消息隊列、發(fā)送消息隊列、應(yīng)答消息隊列和喚醒標(biāo)志(見第2?6章的討論)。
? ? 如果讓所有的線程都共享一個輸入隊列,就會嚴(yán)重削弱系統(tǒng)的強壯性。如果某一個線程接收一個按鍵消息并且掛起,其他的線程就不能接收任何輸入了。所以應(yīng)該盡量避免使用A?t?t?a?c?h?T?h?r?e?a?d?I?n?p?u?t函數(shù)。在某些情況下,系統(tǒng)隱式地將兩個線程掛接在一起。第一種情況是當(dāng)一個線程安裝一個日志記錄掛鉤(journal?record?hook)或日志播放掛鉤(journal?playback?hook)的時候。當(dāng)掛鉤被卸載時,系統(tǒng)自動恢復(fù)所有線程,這樣線程就可以使用掛鉤安裝前它們所使用的相同輸入隊列。
? ? 當(dāng)一個線程安裝一個日志記錄掛鉤時,它是讓系統(tǒng)將用戶輸入的所有硬件事件都通知它。這個線程通常將這些信息保存或記錄在一個文件上。因用戶的輸入必須按進(jìn)入的次序來記錄,所以系統(tǒng)中每個線程要共享一個虛擬輸入隊列,使所有的輸入處理同步。
? ? 還有一些情況,系統(tǒng)會代替你隱式地調(diào)用?A?t?t?a?c?h?T?h?r?e?a?d?I?n?p?u?t。假定你的程序建立了兩個線程。第一個線程建立了一個對話框。在這個對話框建立之后,第二個線程調(diào)用?G?r?e?a?t?Wi?n?d?o?w,使用W?S?_?C?H?I?L?D風(fēng)格,并向這個子窗口的雙親傳遞對話框的句柄。系統(tǒng)用子窗口的線程調(diào)用A?t?t?a?c?h?T?h?r?e?a?d?I?n?p?u?t,讓子窗口的線程使用對話框線程所使用的輸入隊列。這樣就使對話框的所有子窗口之間對輸入強制同步。
總結(jié)
以上是生活随笔為你收集整理的Windows核心编程 第27章 硬件输入模型和局部输入状态的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows核心编程 第26章 窗口消
- 下一篇: WindowsPE 第七章 资源表