Windows核心编程 第26章 窗口消 息
窗?口?消?息
? ? Wi?n?d?o?w?s允許一個進程至多建立10?000個不同類型的用戶對象(User?object):圖符、光標、窗口類、菜單、加速鍵表等等。當一個線程調用一個函數來建立某個對象時,則該對象就歸這個線程的進程所擁有。這樣,當進程結束時,如果沒有明確刪除這個對象,則操作系統會自動刪除這個對象。對窗口和掛鉤(?h?o?o?k?)這兩種U?s?e?r對象,它們分別由建立窗口和安裝掛鉤的線程所擁有。如果一個線程建立一個窗口或安裝一個掛鉤,然后線程結束,操作系統會自動刪除窗口或卸載掛鉤。
26.1?線程的消息隊列
????當一個線程第一次被建立時,系統假定線程不會被用于任何與用戶相關的任務。這樣可以減少線程對系統資源的要求。但是,一旦這個線程調用一個與圖形用戶界面有關的函數(例如檢查它的消息隊列或建立一個窗口),系統就會為該線程分配一些另外的資源,以便它能夠執行與用戶界面有關的任務。特別是,系統分配一個T?H?R?E?A?D?I?N?F?O結構,并將這個數據結構與線程聯系起來。
這個T?H?R?E?A?D?I?N?F?O結構包含一組成員變量,利用這組成員,線程可以認為它是在自己獨占的環境中運行。T?H?R?E?A?D?I?N?F?O是一個內部的、未公開的數據結構,用來指定線程的登記消息隊列(posted-message?queue)、發送消息隊列(?send-message?queue)、應答消息隊列(?r?e?p?l?y?-message?queue)、虛擬輸入隊列(virtualized-input?queue)、喚醒標志?(wake?flag)、以及用來描述線程局部輸入狀態的若干變量。
26.2?將消息發送到線程的消息隊列中
(1)BOOL?WINAPI?PostMessageW(
????_In_opt_?HWND?hWnd,
????_In_?UINT?Msg,
????_In_?WPARAM?wParam,
????_In_?LPARAM?lParam);.
? ? 當一個線程調用這個函數時,系統要確定是哪一個線程建立了用?h?w?n?d參數標識的窗口。然后系統分配一塊內存,將這個消息參數存儲在這塊內存中,并將這塊內存增加到相應線程的登記消息隊列中。并且,這個函數還設置?Q?S?_?P?O?S?T?M?E?S?S?A?G?E喚醒位(后面會簡單討論)。函數P?o?s?t?M?e?s?s?a?g?e在登記了消息之后立即返回,調用該函數的線程不知道登記的消息是否被指定窗口的窗口過程所處理。實際上,有可能這個指定的窗口永遠不會收到登記的消息。如果建立這個特定窗口的線程在處理完它的消息隊列中的所有消息之前就結束了,就會發生這種事。
還可以通過調用P?o?s?t?T?h?r?e?a?d?M?e?s?s?a?g?e將消息放置在線程的登記消息隊列中。
(2)BOOL?WINAPI?PostThreadMessageW(
????_In_?DWORD?idThread,
????_In_?UINT?Msg,
????_In_?WPARAM?wParam,
????_In_?LPARAM?lParam);
? ? P?o?s?t?T?h?r?e?a?d?M?e?s?s?a?g?e函數所期望的線程由第一個參數d?w?T?h?r?e?a?d?I?d所標記。當消息被設置到隊列中時,M?S?G結構的h?w?n?d成員將設置成N?U?L?L。當程序要在主消息循環中執行一些特殊處理時要調用這個函數。
要對線程編寫主消息循環以便在?G?e?t?M?e?s?s?a?g?e或P?e?e?k?M?e?s?s?a?g?e取出一個消息時,主消息循環代碼檢查h?w?n?d是否為N?U?L?L,并檢查M?S?G結構的m?s?g成員來執行特殊的處理。如果線程確定了該消息不被指派給一個窗口,則不調用D?i?s?p?a?t?c?h?M?e?s?s?a?g?e,消息循環繼續取下一個消息。
? ? 像P?o?s?t?M?e?s?s?a?g?e函數一樣,P?o?s?t?T?h?r?e?a?d?M?e?s?s?a?g?e在向線程的隊列登記了消息之后就立即返回。調用該函數的線程不知道消息是否被處理。
向線程的隊列發送消息的函數還有P?o?s?t?Q?u?i?t?M?e?s?s?a?g?e:
(3)VOID?PostQuitMessage(int?nExitCode);
? ?為了終止線程的消息循環,可以調用這個函數。調用P?o?s?t?Q?u?i?t?M?e?s?s?a?g?e類似于調用:
PostThreadMessage(GetCurrentThreadid(),WM_QUIT?,nExitCode?,0);
但是,P?o?s?t?Q?u?i?t?M?e?s?s?a?g?e并不實際登記一個消息到任何一個?T?H?R?E?A?D?I?N?F?O結構的隊列。只是在內部,P?o?s?t?Q?u?i?t?M?e?s?s?a?g?e設定Q?S_Q?U?I?T喚醒標志(后面將要討論),并設置T?H?R?E?A?D?I?N?F?O結構的n?E?x?i?t?C?o?d?e成員。因為這些操作永遠不會失敗,所以?P?o?s?t?Q?u?i?t?M?e?s?s?a?g?e的原型被定義成返回V?O?I?D。
? ? 注意?可以通過調用G?e?t?Wi?n?d?o?w?s?T?h?r?e?a?d?P?r?o?c?e?s?s?I?d來確定是哪個線程建立了一個窗口。
? ? DWORD?GetWindowThreadProcessId(HWND?hwnd?,PDWORD?pdwProcessId);
? ? 這個函數返回線程的I?D?,這個線程建立了h?w?n?d參數所標識的窗口。線程I?D?在全系統范圍內是唯一的。還可以通過對p?d?w?P?r?o?c?e?s?s?I?d參數傳遞一個D?W?O?R?D?地址來獲取擁有該線程的進程I?D?,這個進程I?D?在全系統范圍內也是唯一的。通常,我們不需要進程I?D?,只須對這個參數傳遞一個N?U?L?L。
26.3?向窗口發送消息
? ? 使用S?e?n?d?M?e?s?s?a?g?e函數可以將窗口消息直接發送給一個窗口過程:
LRESULT?WINAPI?SendMessageW(
????_In_?HWND?hWnd,
????_In_?UINT?Msg,
????_Pre_maybenull_?_Post_valid_?WPARAM?wParam,
????_Pre_maybenull_?_Post_valid_?LPARAM?lParam);
? ? 窗口過程將處理這個消息。只有當消息被處理之后,?S?e?n?d?M?e?s?s?a?g?e才能返回到調用程序。由于具有這種同步特性,比之P?o?s?t?M?e?s?s?a?g?e或P?o?s?t?T?h?r?e?a?d?M?e?s?s?a?g?e,S?e?n?d?M?e?s?s?a?g?e用得更頻繁。調用這個函數的線程在下一行代碼執行之前就知道窗口消息已經被完全處理。
? ? S?e?n?d?M?e?s?s?a?g?e是如何工作的呢?如果調用S?e?n?d?M?e?s?s?a?g?e的線程向該線程所建立的一個窗口發送一個消息,S?e?n?d?M?e?s?s?a?g?e就很簡單:它只是調用指定窗口的窗口過程,將其作為一個子例程。當窗口過程完成對消息的處理時,它向?S?e?n?d?M?e?s?s?a?g?e返回一個值。S?e?n?d?M?e?s?s?a?g?e再將這個值返回給調用線程。
? ? 但是,當一個線程向其他線程所建立的窗口發送消息時,?S?e?n?d?M?e?s?s?a?g?e的內部工作就復雜得多(即使兩個線程在同一進程中也是如此)。Wi?n?d?o?w?s要求建立窗口的線程處理窗口的消息。所以當一個線程用S?e?n?d?M?e?s?s?a?g?e向一個由其他進程所建立的窗口發送一個消息,也就是向其他的線程發送消息,發送線程不可能處理窗口消息,因為發送線程不是運行在接收進程的地址空間中,因此不能訪問相應窗口過程的代碼和數據。實際上,發送線程要掛起,而由另外的線程處理消息。所以為了向其他線程建立的窗口發送一個窗口消息,系統必須執行下面將討論的動作。
? ? 首先,發送的消息要追加到接收線程的發送消息隊列,同時還為這個線程設定Q?S?_?S?E?N?D?M?E?S?S?A?G?E標志(后面將討論)。其次,如果接收線程已經在執行代碼并且沒有等待消息(如調用G?e?t?M?e?s?s?a?g?e、P?e?e?k?M?e?s?s?a?g?e或Wa?i?t?M?e?s?s?a?g?e),發送的消息不會被處理,系統不能中斷線程來立即處理消息。當接收進程在等待消息時,系統首先檢查?Q?S?_?S?E?N?D?M?E?S?S?A?G?E喚醒標志是否被設定,如果是,系統掃描發送消息隊列中消息的列表,并找到第一個發送的消息。有可能在這個隊列中有幾個發送的消息。例如,幾個線程可以同時向一個窗口分別發送消息。當發生這樣的事時,系統只是將這些消息追加到接收線程的發送消息隊列中。
? ? 當接收線程等待消息時,系統從發送消息隊列中取出第一個消息并調用適當的窗口過程來處理消息。如果在發送消息隊列中再沒有消息了,則?Q?S?_?S?E?N?D?M?E?S?S?A?G?E喚醒標志被關閉。當接收線程處理消息的時候,調用?S?e?n?d?M?e?s?s?a?g?e的線程被設置成空閑狀態(i?d?l?e),等待一個消息出現在它的應答消息隊列中。在發送的消息處理之后,窗口過程的返回值被登記到發送線程的應答消息隊列中。發送線程現在被喚醒,取出包含在應答消息隊列中的返回值。這個返回值就是調用S?e?n?d?M?e?s?s?a?g?e的返回值。這時,發送線程繼續正常執行。
? ? 當一個線程等待S?e?n?d?M?e?s?s?a?g?e返回時,它基本上是處于空閑狀態。但它可以執行一個任務:如果系統中另外一個線程向一個窗口發送消息,這個窗口是由這個等待?S?e?n?d?M?e?s?s?a?g?e返回的線程所建立的,則系統要立即處理發送的消息。在這種情況下,系統不必等待線程去調用G?e?t?M?e?s?s?a?g?e、Peek?Message或Wa?i?t?M?e?s?s?a?g?e。
? ? 由于Wi?n?d?o?w?s使用上述方法處理線程之間發送的消息,所以有可能造成線程掛起(?h?a?n?g)。例如,當處理發送消息的線程含有錯誤時,會導致進入死循環。那么對于調用?S?e?n?d?M?e?s?s?a?g?e的線程會發生什么事呢?它會恢復執行嗎?這是否意味著一個程序中的?b?u?g會導致另一個程序掛起?答案是確實有這種可能。
? ? 利用4個函數——S?e?n?d?M?e?s?s?a?g?e?Ti?m?e?o?u?t、S?e?n?d?M?e?s?s?a?g?e?C?a?l?l?b?a?c?k、S?e?n?d?N?o?t?i?f?y?M?e?s?s?a?g?e和R?e?p?l?y?M?e?s?s?a?g?e,可以編寫保護性代碼防止出現這種情況。第一個函數是?S?e?n?d?M?e?s?s?a?g?e?Ti?m?e?o?u?t:
LRESULT?WINAPI?SendMessageTimeoutW(
????_In_?HWND?hWnd,
????_In_?UINT?Msg,
????_In_?WPARAM?wParam,
????_In_?LPARAM?lParam,
????_In_?UINT?fuFlags,
????_In_?UINT?uTimeout,
????_Out_opt_?PDWORD_PTR?lpdwResult);
? ? 利用S?e?n?d?M?e?s?s?a?g?e?Ti?m?e?o?u?t函數,可以規定等待其他線程答回你消息的時間最大值。前?4個參數與傳遞給S?e?n?d?M?e?s?s?a?g?e的參數相同。對f?u?F?l?a?g?s參數,可以傳遞值S?M?TO?_?N?O?R?M?A?L?(定義為0?)、S?M?TO?_?A?B?O?RT?I?F?H?U?N?G、S?M?TO?_?B?L?O?C?K、S?M?TO?_?N?O?T?I?M?E?O?U?T?I?F?N?O?T?H?U?N?G或這些標志的組合。
? ? S?M?TO?_?A?B?O?RT?I?F?H?U?N?G標志是告訴S?e?n?d?M?e?s?s?a?g?e?Ti?m?e?o?u?t去查看接收消息的線程是否處于掛起狀態,如果是,就立即返回。S?M?TO?_?N?O?T?I?M?E?O?U?T?I?F?N?O?T?H?U?N?G標志使函數在接收消息的線?程?沒?有?掛?起?時?不?考?慮?等?待?時?間?限?定?值?。?S?M?T?O?_?B?L?O?C?K?標?志?使?調?用?線?程?在S?e?n?d?M?e?s?s?a?g?e?Ti?m?e?o?u?t返回之前,不再處理任何其他發送來的消息。?S?M?TO?_?N?O?R?M?A?L標志在Wi?n?u?s?e?r.?h中定義成0,如果不想指定任何其他標志及組合,就使用這個標志。
? ? 前面說過,一個線程在等待發送的消息返回時可以被中斷,以便處理另一個發送來的消息。使用S?M?TO?_?B?L?O?C?K標志阻止系統允許這種中斷。僅當線程在等待處理發送的消息的時候?(不能處理別的發送消息),才使用這個標志。使用S?M?TO?_?B?L?O?C?K可能會產生死鎖情況,直到等待時間期滿。例如,如果你的線程向另外一個線程發送一個消息,而這個線程又需要向你的線程發送消息。在這種情況下,兩個線程都不能繼續執行,并且都將永遠掛起。
? ? S?e?n?d?M?e?s?s?a?g?e?Ti?m?e?o?u?t函數中的u?Ti?m?e?o?u?t參數指定等待應答消息時間的毫秒數。如果這個函數執行成功,返回T?R?U?E,消息的結果復制到一個緩沖區中,該緩沖區的地址由?p?d?w?R?e?s?u?l?t參數指定。
用來在線程間發送消息的第二個函數是S?e?n?d?M?e?s?s?a?g?e?C?a?l?l?b?a?c?k:
BOOL?WINAPI?SendMessageCallbackW(
????_In_?HWND?hWnd,
????_In_?UINT?Msg,
????_In_?WPARAM?wParam,
????_In_?LPARAM?lParam,
????_In_?SENDASYNCPROC?lpResultCallBack,
????_In_?ULONG_PTR?dwData);
? ? 同樣,前4個參數同S?e?n?d?M?e?s?s?a?g?e中使用的一樣。當一個線程調用S?e?n?d?M?e?s?s?a?g?e?C?a?l?l?b?a?c?k時,該函數發送消息到接收線程的發送消息隊列,并立即返回使發送線程可以繼續執行。當接收線程完成對消息的處理時,一個消息被登記到發送線程的應答消息隊列中。然后,系統通過調用一個函數將這個應答通知給發送線程,該函數是使用下面的原型編寫的。
VOID?CALLBACK?ResultCallBack(
????HWND?hwnd,
????UINT?uMsg,
????ULONG_PTR?dwData,
????LRESULT?lResult);
? ? 因為S?e?n?d?M?e?s?s?a?g?e?C?a?l?l?b?a?c?k在執行線程間發送時會立即返回,所以在接收線程完成對消息的處理時不是立即調用這個回調函數。而是由接收線程先將一個消息登記到發送線程的應答消息隊列。發送線程在下一次調用G?e?t?M?e?s?s?a?g?e、P?e?e?k?M?e?s?s?a?g?e、Wa?i?t?M?e?s?s?a?g?e或某個S?e?n?d?M?e?s?s?a?g?e*函數時,消息從應答消息隊列中取出,并執行R?e?s?u?l?t?C?a?l?l?B?a?c?k函數。
? ? S?e?n?d?M?e?s?s?a?g?e?C?a?l?l?b?a?c?k函數還有另外一個用處。Wi?n?d?o?w?s提供了一種廣播消息的方法,用這種方法你可以向系統中所有現存的重疊(?o?v?e?r?l?a?p?p?e?d)窗口廣播一個消息。這可以通過調用S?e?n?d?M?e?s?s?a?g?e函數,對參 數h?w?n?d傳遞H?W?N?D?_?B?R?O?A?D?C?A?S?T(定義為-1)。使用這種方法廣播的消息,其返回值我們并不感興趣,因為?S?e?n?d?M?e?s?s?a?g?e函數只能返回一個?L?R?E?S?U?LT。但使用S?e?n?d?M?e?s?s?a?g?e?C?a?l?l?b?a?c?k,就可以向每一個重疊窗口廣播消息,并查看每一個返回結果。對每一個處理消息的窗口的返回結果都要調用R?e?s?u?l?t?C?a?l?l?b?a?c?k函數。
? ? 如果調用S?e?n?d?M?e?s?s?a?g?e?C?a?l?l?b?a?c?k向一個由調用線程所建立的窗口發送一個消息,系統立即調用窗口過程,并且在消息被處理之后,系統調用?R?e?s?u?l?t?C?a?l?l?B?a?c?k函數。在R?e?s?u?l?t?C?a?l?l?B?a?c?k函數返回之后,系統從調用S?e?n?d?M?e?s?s?a?g?e?C?a?l?l?b?a?c?k的后面的代碼行開始執行。
? ? 線程間發送消息的第三個函數是S?e?n?d?N?o?t?i?f?y?M?e?s?s?a?g?e
BOOL?WINAPI?SendNotifyMessageW(
????_In_?HWND?hWnd,
????_In_?UINT?Msg,
????_In_?WPARAM?wParam,
????_In_?LPARAM?lParam);
? ? S?e?n?d?N?o?t?i?f?y?M?e?s?s?a?g?e將一個消息置于接收線程的發送消息隊列中,并立即返回到調用線程。這一點與P?o?s?t?M?e?s?s?a?g?e函數一樣,但S?e?n?d?N?o?t?i?f?y?M?e?s?s?a?g?e在兩方面與P?o?s?t?M?e?s?s?a?g?e不同。
? ? 首先,S?e?n?d?N?o?t?i?f?y?M?e?s?s?a?g?e是向另外的線程所建立的窗口發送消息,發送的消息比起接收線程消息隊列中存放的登記消息有更高的優先級。換句話說,由?S?e?n?d?N?o?t?i?f?y?M?e?s?s?a?g?e函數存放在隊列中的消息總是在P?o?s?t?M?e?s?s?a?g?e函數登記到隊列中的消息之前取出。
? ? 其次,當向一個由調用進程建立的窗口發送消息時,?S?e?n?d?N?o?t?i?f?y?M?e?s?s?a?g?e同S?e?n?d?M?e?s?s?a?g?e函數完全一樣:S?e?n?d?N?o?t?i?f?y?M?e?s?s?a?g?e在消息被處理完之后才能返回。
? ? 第四個用于線程發送消息的函數是R?e?p?l?y?M?e?s?s?a?g?e:
BOOL?ReplyMessage(LRESULT?lResult);
? ? 這個函數與前面討論過的三個函數不同。線程使用?S?e?n?d?M?e?s?s?a?g?e?Ti?m?e?o?u?t、S?e?n?d?M?e?s?s?a?g?eC?a?l?l?b?a?c?k和S?e?n?d?N?o?t?i?f?y?M?e?s?s?a?g?e發送消息,是為了保護自己以免被掛起。而線程調用?R?e?p?l?yM?e?s?s?a?g?e是為了接收窗口消息。當一個線程調用?R?e?p?l?y?M?e?s?s?a?g?e時,它是要告訴系統,為了知道消息結果,它已經完成了足夠的工作,結果應該包裝起來并登記到發送線程的應答消息隊列中。這將使發送線程醒來,獲得結果,并繼續執行。
? ? 有時候,你可能想知道究竟是在處理線程間的消息發送,還是在處理線程內的消息發送。
為了搞清楚這一點,可以調用I?n?S?e?n?d?M?e?s?s?a?g?e:
BOOL?InSendMessage();
? ? 還可以調用另外一個函數來確定窗口過程正在處理的消息類型:
DWORD?InSendMessageEx(PVOID?pvReserved);
26.4?喚醒一個線程
? ? 當一個線程調用G?e?t?M?e?s?s?a?g?e或Wa?i?t?M?e?s?s?a?g?e,但沒有對這個線程或這個線程所建立窗口的消息時,系統可以掛起這個線程,這樣就不再給它分配?C?P?U時間。當有一個消息登記或發送到這個線程,系統要設置一個喚醒標志,指出現在要給這個線程分配?C?P?U時間,以便處理消息。正常情況下,如果用戶不按鍵或移動鼠標,就沒有消息發送給任何窗口。這意味著系統中大多數線程沒有被分配給C?P?U時間。
26.4.1?隊列狀態標志
? ? 當一個線程正在運行時,它可以通過調用G?e?t?Q?u?e?u?e?S?t?a?t?u?s函數來查詢隊列的狀態:
DWORD?GetQueueStatus(UNIT?fuFlags)
26.4.2?從線程的隊列中提取消息的算法
? ? 當一個線程調用G?e?t?M?e?s?s?a?g?e或P?e?e?k?M?e?s?s?a?g?e時,系統必須檢查線程的隊列狀態標志的情況,并確定應該處理哪個消息。圖2?6?-?2和下面敘述的步驟說明了系統是如何確定線程應該處理的下一個消息的情況。
26.4.3?利用內核對象或隊列狀態標志喚醒線程
? ? G?e?t?M?e?s?s?a?g?e或P?e?e?k?M?e?s?s?a?g?e函數導致一個線程睡眠,直到該線程需要處理一個與用戶界(U?I)相關的任務。有時候,若能讓線程被喚醒去處理一個與?U?I有關的任務或其他任務,就會帶來許多方便。例如,一個線程可能啟動一個長時間運行的操作,并可以讓用戶取消這個操作。這個線程需要知道何時操作結束(與?U?I無關的任務),或用戶是否按了C?a?n?c?e?l按鈕(與U?I相關的任務)來結束操作。
? ? 一個線程可以調用M?s?g?Wa?i?t?F?o?r?M?u?l?t?i?p?l?e?O?b?j?e?c?t?s或M?s?g?Wa?i?t?F?o?r?M?u?l?t?i?p?l?e?O?b?j?e?c?t?s?E?x函數,使線程等待它自已的消息。
DWORD?MsgWaitForMultipleObjectsEx(
????DWORD?nCount,
????PHANDLE?phObject,
????DWORD?dwMilliseconds,
????DWORD?dwWakeMask,
????DWORD?dwFlags);
?M?s?g?Wa?i?t?F?o?rM?u?l?t?i?p?l?e?O?b?j?e?c?t?s?E?x是M?s?g?Wa?i?t?F?o?r?M?u?l?t?i?p?l?e?O?b?j?e?c?t?s的一個超集(?s?u?p?e?r?s?e?t)。新的特性是通過d?w?F?l?a?g?s參數引進的。對這個參數,可以指定下面標志的任意組合(見表?2?6?-?3)。
????如果不想要任何這些附加的特性,可對參數d?w?F?l?a?g?s傳遞零(0)。
下面是有關M?s?g?Wa?i?t?F?o?r?M?u?l?t?i?p?l?e?O?b?j?e?c?t?s(E?x)的一些重要內容:
??由于這個函數只是向內核句柄的數組增加一個內部事件內核對象,?n?C?o?u?n?t參數的最大值是M?A?X?I?M?U?M?_?WA?I?T?_?O?B?J?E?C?T減1或6?3。
??當對f?Wa?i?t?A?l?l參數傳遞FA?L?S?E時,那么當一個內核對象是有信號的(s?i?g?n?a?l?e?d),或當指定的消息類型出現在線程的隊列時,函數返回。
??當對f?Wa?i?t?A?l?l參數傳遞T?R?U?E時,那么當所有內核對象成為有信號狀態,并且指定的消息類型出現在線程的隊列中時,函數返回。這種行為似乎使許多開發人員感到驚訝。開發人員希望有一種辦法,當所有內核對象變成有信號的或者當指定的消息類型出現在線程
的隊列中時,可以喚醒線程。但沒有函數能夠這樣。
??當調用這兩個函數時,實際是查看是否有指定類型的新消息被放入調用線程的隊列。注意,上述最后一條會使許多開發人員吃驚。這里有一個例子。假定一個線程的隊列目前包含有兩個按鍵消息。如果這個線程現在要調用?M?s?g?Wa?i?t?F?o?r?M?u?l?t?i?p?l?e?O?b?j?e?c?t?s(E?x),其中d?w?Wa?k?e?M?a?s?k參數設置成Q?S?_?I?N?P?U?T,線程將被喚醒,從隊列中取出第一個按鍵消息,并處理這個消息。現在,如果這個線程要再調用?M?s?g?Wa?i?t?F?o?r?M?u?l?t?i?p?l?e?O?b?j?e?c?t?s(E?x),線程將不會被喚醒,因為線程的隊列中沒有“新”的消息。
????對開發人員來說,這已變成了一個主要問題,為此微軟增加了?M?W?M?O?_?I?N?P?U?TAVA?I?LA?B?L?E標志,這只用于M?s?g?Wa?i?t?F?o?r?M?u?l?t?i?p?l?e?O?b?j?e?c?t?s?E?x,而不用于M?s?g?Wa?i?t?F?o?r?M?u?l?t?i?p?l?e?O?b?j?e?c?t?s。
這里是一個例子,講述如何適當地編碼一個使用M?s?g?Wa?i?t?F?o?r?M?u?l?t?i?p?l?e?O?b?j?e?c?ts?E?x的消息循?
?
26.5?通過消息發送數據
? ? 本節將討論系統如何利用窗口消息在進程之間傳送數據。一些窗口消息在其?l?P?a?r?a?m參數中指出了一個內存塊的地址。例如,?W?M?_?S?E?T?T?E?X?T消息使用l?P?a?r?a?m參數作為指向一個以零結尾的字符串的指針,這個字符串為窗口規定了新的文本標題串。考慮下面的調用:
SendMessage(FindWindow(NULL?,”?C?a?l?c?u?l?a?t?o?r”)?,WM_SETTEXT?,
0?,(LPARAM)”XXXXXXX”)
這個調用看起來不會有害。它確定?C?a?l?c?u?l?a?t?o?r程序窗口的窗口句柄,并試圖將窗口的標題改成“A?Test?Caption”。
微軟建立了一個特殊的窗口消息,W?M?_?C?O?P?Y?D?ATA以解決這個(中間數據共享)問題:
COPYDATASTRUCT?cds;
SendMessage(hwndReceicer,WM_COPYDATA?,(WPARAM)hwndSender,(LPARAM)?&cds);
C?O?P?Y?D?ATA?S?T?R?U?C?T是一個結構,定義在Wi?n?U?s?e?r.?h文件中,形式如下面的樣子:
typedef?struct?tagCOPYDATASTRUCT?{
????ULONG_PTR?dwData;
????DWORD?cbData;
????_Field_size_bytes_(cbData)?PVOID?lpData;
}?COPYDATASTRUCT,?*PCOPYDATASTRUCT;
????當一個進程要向另一個進程的窗口發送一些數據時,必須先初始化?C?O?P?Y?D?ATA?S?T?R?U?C?T結構。數據成員d?w?D?a?t?a是一個備用的數據項,可以存放任何值。例如,你有可能向另外的進程發送不同類型或不同類別的數據。可以用這個數據來指出要發送數據的內容。
???c?b?D?a?t?a數據成員規定了向另外的進程發送的字節數,?l?p?D?a?t?a數據成員指向要發送的第一個字節。l?p?D?a?t?a所指向的地址,當然在發送進程的地址空間中。
???當S?e?n?d?M?e?s?s?a?g?e看到要發送一個W?M?_?C?O?P?Y?D?ATA消息時,它建立一個內存映像文件,大小是c?b?D?a?t?a字節,并從發送進程的地址空間中向這個內存映像文件中復制數據。然后再向目的窗口發送消息。在接收消息的窗口過程處理這個消息時,?l?P?a?r?a?m參數指向已在接收進程地址空間的一個C?O?P?Y?D?ATA?S?T?R?U?C?T結構。這個結構的l?p?D?a?t?a成員指向接收進程地址空間中的共享內存映像文件的視圖。
總結
以上是生活随笔為你收集整理的Windows核心编程 第26章 窗口消 息的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows核心编程 第25章 未处理
- 下一篇: Windows核心编程 第27章 硬件输