天天看點

windows的視窗消息機制(讀windows核心程式設計筆記):基礎篇

當線程調用函數來建立某個對象時,則該對象就歸這個線程的程序所擁有。這樣,當程序結束時,如果沒有明确删除這個對象,則作業系統會自動删除這個對象。對視窗和挂鈎( h o o k )這兩種U s e r對象,它們分别由建立視窗和安裝挂鈎的線程所擁有。

如果一個線程建立一個視窗或安裝一個挂鈎,線程結束,作業系統會自動删除視窗或解除安裝挂鈎。這意味:建立視窗的線程必須為視窗處理所有消息。這也意味着每個線程,如果至少建立了一個視窗,都由系統對它配置設定一個消息隊列。這個隊列用于視窗消息的派送( d i s p a t c h)。為了使視窗接收這些消息,線程必須有它自己的消息循環。

線程調用一個與圖形使用者界面有關的函數(例如檢查它的消息隊列或建立一個視窗),系統就會為該線程配置設定一些另外的資源,以便它能夠執行與使用者界面有關的任務。特别是,系統配置設定一個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)、以及用來描述線程局部輸入狀态的若幹變量,離開代碼(int nExitCode).這個T H R E A D I N F O結構是視窗消息系統的基礎.

BOOL PostMessage(

  HWND hWnd,      // handle of destination window-------Long,接收消息的那個視窗的句柄。

  UINT Msg,       // message to post----------------------------Long,消息辨別符

  WPARAM wParam,  // first message parameter

  LPARAM lParam   // second message parameter

); 作用是消息被放置線上程的登記消息隊列中。将一條消息投遞到指定視窗的消息隊列。投遞的消息會在Windows事件處理過程中得到處理。特别适合那些不需要立即處理的視窗消息的發送。

hWnd如設為HWND_BROADCAST,表示投遞給系統中的所有頂級視窗。如設為零,表示投遞一條線程消息。

當一個線程調用這個函數時,系統要确定是哪一個線程建立了用h w n d參數辨別的視窗。然後系統配置設定一塊記憶體,将這個消息參數存儲在這塊記憶體中,并将這塊記憶體增加到相應線程的登記消息隊列中。并且,這個函數還設定Q S _ P O S T M E S S A G E喚醒位。

函數在登記了消息之後立即傳回,調用該函數的線程不知道登記的消息是否被指定視窗的視窗過程所處理。實際上,有可能這個指定的視窗永遠不會收到登記的消息。如果建立這個特定視窗的線程在處理完它的消息隊列中的所有消息之前就結束了,就會發生這種事。

DWORD GetWindowThreadProcessId(

  HWND hWnd,             // handle to window

  LPDWORD lpdwProcessId // address of variable for process identifier

);函數傳回線程的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。

BOOL PostThreadMessage(

  DWORD idThread, // thread identifier

  UINT Msg,       // message to post

  WPARAM wParam,  // first message parameter

  LPARAM lParam   // second message parameter

);也可以将消息放置線上程的登記消息隊列中。PostThreadMessage是一個線程體發送一個消息到指定的線程ID。

當消息被設定到隊列中時,M S G結構的h w n d成員将設定成N U L L。當程式要在主消息循環中執行一些特殊處理時要調用這個函數。這個函數既可以發送消息給工作線程,也可以發送給UI線程。

接受PostThreadMessage的線程必須已經有了一個message queue,否則調用PostThreadMessage會失敗。因為此原因使用GetLastError會得到錯誤碼為1444,這種情況經常出現,解決方法有如下兩種:調用PostThreadMessage,如果失敗,就Sleep一段時間再次調PostThreadMessage直到調用成功;建立一個Event對象,讓PostThreadMessage等待接受的線程建立一個message queue。可以通過調用PeekMessage強制系統建立一個message queue。

PostThreadMessage傳遞的消息如果要包含資訊,要注意在結束的時候釋放消息中的資訊。P o s t T h r e a d M e s s a g e在向線程的隊列登記了消息之後就立即傳回。調用該函數的線程不知道消息是否被處理。

VOID PostQuitMessage(
  int nExitCode   // exit code
);為了終止線程的消息循環,應該是由DestroyWindow()中發出的WM_QUIT消息來引起調用的。
PostQuitMessage()是posts一個WM_QUIT消息給消息隊列并立即傳回;程序收到WM_QUIT消息後退出
消息循環,程式結束。DestroyWindow()是發送一個WM_DESTROY消息,結束掉此視窗,但是程式并
沒有結束。
               

LRESULT SendMessage(

  HWND hWnd,      // handle of destination window

  UINT Msg,       // message to send

  WPARAM wParam,  // first message parameter

  LPARAM lParam   // second message parameter

);

函數功能是将視窗消息直接發送給一個視窗過程:隻有當消息被處理之後, 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向一個由其他程序所建立的視窗發送一個消息,發送線程不可能處理視窗消息,因為發送線程不是運作在接收程序的位址空間中,是以不能通路相應視窗過程的代碼和資料。實際上,發送線程要挂起,而由另外的線程處理消息。是以為了向其他線程建立的視窗發送一個視窗消息,系統必須執行下面将讨論的動作。

首先,發送的消息要追加到接收線程的發送消息隊列,同時還為這個線程設定Q S _ S E N D M E S S A GE标志。

其次,如果接收線程已經在執行代碼并且沒有等待消息(如調用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 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,可以編寫保護性代碼防止出現這種情況。

LRESULT SendMessageTimeout(

  HWND hWnd,           // handle of destination window:其視窗程式将接收消息的視窗的句柄

  UINT Msg,            // message to send

  WPARAM wParam,       // first message parameter

  LPARAM lParam,       // second message parameter

  UINT fuFlags,        // how to send the message

  UINT uTimeout,       // time-out duration

  LPDWORD lpdwResult   // return value for synchronous call

);函數功能:該函數将指定的消息發送到一個或多個視窗。此函數為指定的視窗調用視窗程式,并且,如果指定的視窗屬于不同的線程,直到視窗程式處理完消息或指定的逾時周期結束函數才傳回。如果接收消息的視窗和目前線程屬于同一個隊列,視窗程式立即調用,逾時值無用。

對fuFlags參數,可以傳遞值SMTO_NORMAL(定義為0 )、SMTO_ABORTIFHUNG、SMTO_BLOCK、SMTO_NOTIMEOUTIFNOTHUNG或這些标志的組合。

SMTO_ABORTIFHUNG是告訴SendMessageTimeout去檢視接收消息的線程是否處于挂起狀态,如果是,就立即傳回。SMTO_NOTIMEOUTIFNOTHUNG标志使函數在接收消息的線程沒有挂起時不考慮等待時間限定值。SMTO_BLOCK标志使調用線程在SendMessageTimeout傳回之前,不再處理任何其他發送來的消息。SMTO_NORMAL調用線程等待函數傳回時,不被阻止處理其他請求。在Wi n u s e r. h中定義成0,如果不想指定任何其他标志及組合,就使用這個标志。

一個線程在等待發送的消息傳回時可以被中斷,以便處理另一個發送來的消息。使用S M TO _ B L O C K标志阻止系統允許這種中斷。僅當線程在等待處理發送的消息的時候(不能處理别的發送消息),才使用這個标志。使用S M TO _ B L O C K可能會産生死鎖情況,直到等待時間期滿。例如,如果你的線程向另外一個線程發送一個消息,而這個線程又需要向你的線程發送消息。在這種情況下,兩個線程都不能繼續執行,并且都将永遠挂起。

SendMessageTimeout函數中的uTimeout參數指定等待應答消息時間的毫秒數。如果這個函數執行成功,傳回TRUE,消息的結果複制到一個緩沖區中,該緩沖區的位址由pdwResult參數指定。如果函數是由于等待逾時而失敗,則GetLastError為0(ERROR_SUCCESS)。如果對參數傳遞了一個無效句柄,GetLastError為1400(ERROR_INVALID_WINDOW_HANDLE)。

如果調用S endMessageTimeout向調用線程所建立的視窗發送一個消息,系統隻是調用這個視窗的視窗過程,并将傳回值賦給pdwResult。因為所有的處理都必須發生在一個線程裡,調用SendMessageTimeout函數之後出現的代碼要等消息被處理完之後才能開始執行。

注:如果該消息是一個廣播消息,每個視窗可使用全逾時周期。例如,如果指定5秒的逾時周期,有3個頂層窗回未能處理消息,可以有最多15秒的延遲。

BOOL SendMessageCallback(

  HWND hWnd,     // handle of destination window

  UINT Msg,      // message to send

  WPARAM wParam, // first message parameter

  LPARAM lParam, // second message parameter

  SENDASYNCPROC lpResultCallBack,

                 // function to receive message value

  DWORD dwData   // value to pass to callback function

);函數功能:該函數将指定的消息發送到一個或多個視窗。此函數為指定的視窗調用視窗程式,并立即傳回。當視窗程式處理完消息後,系統調用指定的回調函數,将消息處理的結果和一個應用程式定義的值傳給回調函數。

該函數發送消息到接收線程的發送消息隊列,并立即傳回使發送線程可以繼續執行。當接收線程完成對消息的處理時,一個消息被登記到發送線程的應答消息隊列中。然後,系統通過調用一個函數将這個應答通知給發送線程,該函數是使用下面的原型編寫的。

參數 hWnd:其視窗程式将接收消息的視窗的句柄。如果此參數為HWND_BROADCAST,則消息将被發送到系統中所有頂層視窗,包括無效或不可見的非自身擁有的視窗、被覆寫的視窗和彈出式視窗,但消息不被發送到子視窗。    

Msg:指定被發送的消息。    

wParam:指定附加的消息指定資訊。    

IParam:指定附加的消息指定資訊。    

IpResultCallBack:指向回收函數的指針,窗曰程式處理完消息後調用該回調函數。參見SendAsyncProc可得到合适的回調函數的資訊。如果hwnd為HWND_BROADCAST,系統為每個頂層視窗調用一次SendASyncProc回調函數。    

dwData:一個應用程式定義的值,被傳給由參數IPResultCallBack指向的回調函數。    

傳回值:如果函數調用成功,傳回非零值。如果函數調用失敗,傳回值是零。若想獲得更多的錯誤資訊,請調用GetLastError函數

備注:如果發送一個低于WM_USER範圍的消息給異步消息函(PostMessage,SendNotifyMesssge;SendMessageCallback),消息參數不能包含指針。否則,操作将會失敗。函數将在接收線程處理消息之前傳回,發送者将在記憶體被使用之前釋放。     需要以HWND_BROADCAST方式通信的應用程式應當用函數RegisterWindwosMessage來獲得應用程式間通信的獨特的消息。     此回調函數僅當調用SendMessagecallback的線程調用GetMessage,PeekMessage或WaitMessage時調用。    

該函數發送消息到接收線程的發送消息隊列,并立即傳回使發送線程可以繼續執行。當接收線程完成對消息的處理時,一個消息被登記到發送線程的應答消息隊列中。然後,系統通過調用一個函數将這個應答通知給發送線程,該函數是使用下面的原型編寫的。

VOID CALLBACK ResultCallBack(

   HWND hwnd,

   UINT uMsg,

   ULONG_PTR dwData,

   LRESULT lResult);

必須将這個函數的位址傳遞給S e n d M e s s a g e Callback函數作為pfnResultCallbac k參數值。當調用這個函數時,要把完成消息處理的視窗的句柄傳遞到第一個參數,将消息值傳遞給第二個參數。第三個參數dwData,總是取傳遞到SendMessageCallback函數的dwData參數的值。系統隻是取出對SendMessageCallback函數指定的參數值,再直接傳遞到ResultCallback函數。ResultCallback函數的最後一個參數是處理消息的視窗過程傳回的結果。因為SendMessageCallback在執行線程間發送時會立即傳回,是以在接收線程完成對消息的處理時不是立即調用這個回調函數。而是由接收線程先将一個消息登記到發送線程的應答消息隊列。發送線程在下一次調用GetMessage、PeekMessage、WaitMessage或某個SendMessage*函數時,消息從應答消息隊列中取出,并執行ResultCallBack函數。

SendMessageCallback函數還有另外一個用處。Windows提供了一種廣播消息的方法,用這種方法你可以向系統中所有現存的重疊(overlapped)視窗廣播一個消息。這可以通過調用SendMessage函數,對參數hwnd傳遞HWND_BROADCAST(定義為-1)。使用這種方法廣播的消息,其傳回值我們并不感興趣,因為SendMessage函數隻能傳回一個LR ESULT。但使用SendMessageCallback,就可以向每一個重疊視窗廣播消息,并檢視每一個傳回結果。對每一個處理消息的視窗的傳回結果都要調用ResultCallback函數。

如果調用SendMessageCallback向一個由調用線程所建立的視窗發送一個消息,系統立即調用視窗過程,并且在消息被處理之後,系統調用ResultCallBack函數。在ResultCallBack函數傳回之後,系統從調用SendMessageCallback的後面的代碼行開始執行。

BOOL SendNotifyMessage(

  HWND hWnd,      // handle of destination window

  UINT Msg,       // message to send

  WPARAM wParam,  // first message parameter

  LPARAM lParam   // second message parameter

);

 函數功能:該函數将指定的消息發送到一個視窗。如果該視窗是由調用線程建立的;此函數為該視窗調用視窗程式,并等待視窗程式處理完消息後再傳回。如果該視窗是由不同的線程建立的,此函數将消息傳給該視窗程式,并立即傳回,不等待視窗程式處理完消息。  

參數:hWnd:其視窗程式将接收消息的視窗的句柄。如果此參數為HWND_BROADCAST,則消息将被發送到系統中所有頂層視窗,包括無效或不可見的非自身擁有的視窗、被覆寫的視窗和彈出式視窗,但消息不被發送到子視窗。    

Msg:指定被發送的消息。 

wParam:指定附加的消息指定資訊。    

IParam:指定附加的消息指定資訊。    

傳回值:如果函數調用成功,傳回非零值;如果函數調用失敗,傳回值是零。若想獲得更多的錯誤資訊,請調用GetLastError函數。    

備注:如果發送一個低于WM_USER範圍的消息給異步消息函數(PostMessage,SendNotifyMessage,SendMesssgeCallback),消息參數不能包含指針。否則,操作将會失敗。函數将在接收線程處理消息之前傳回,發送者将在記憶體被使用之前釋放。     需要以HWND_BROADCAST方式通信的應用程式應當用函數RegisterWindwosMessage來獲得應用程式間通信的獨特的消息。

SendNotifyMessage和SendNotifyMessage的差別:

相同點:一個消息置于接收線程的發送消息隊列中,并立即傳回到調用線程。

不同點:首先,SendNotifyMessage是向另外的線程所建立的視窗發送消息,發送的消息比起接收線程消息隊列中存放的登記消息有更高的優先級。換句話說,由SendNotifyMessage函數存放在隊列中的消息總是在PostMessage函數登記到隊列中的消息之前取出。

其次,當向一個由調用程序建立的視窗發送消息時,SendNotifyMessage同SendMessage函數完全一樣:SendNotifyMessage在消息被處理完之後才能傳回。    

注意:發送給視窗的大多數消息是用于通知的目的。也就是,發送消息是因為視窗需要知道某個狀态已經發生變化,在程式能夠繼續執行之前,視窗要做某種處理。例如,WM_ACTIVATE、WM_DESTROY、WM_ENABLE、WM_SIZE、WM_SETFOCUS和WM_MOVE等都是系統發送給視窗的通知,而不是登記的消息。這些消息是系統對視窗的通知,是以系統不需要停止運作以等待視窗過程處理這些消息。與此相對應,如果系統向一個視窗發送一個WM_CREATE消息,則在視窗處理完這個消息之前,系統必須等待。如果傳回值是-1,則不再建立視窗。

BOOL ReplyMessage(LRESULT lResult // message-specific reply );

 函數功能:該函數用于應答由函數SendMessage發送的消息,不傳回控制給調用SendMessage的函數。它是要告訴系統,為了知道消息結果,它已經完成了足夠的工作,結果應該包裝起來并登記到發送線程的應答消息隊列中。這将使發送線程醒來,獲得結果,并繼續執行。

IResult:指定消息處理的結果。可能的值由所發送的消息确定。在調用ReplyMessage之後,發送消息的線程恢複執行,而處理消息的線程繼續處理消息。兩個線程都不會被挂起,都可以正常執行。當處理消息的線程從它的視窗過程傳回時,它傳回的任何值都被忽略。

傳回值:如果調用線程正處理從其他線程或程序發送的消息,傳回非零值。如果調用線程不是正處理從其他線程或程序發送的消息,傳回值是零。

備注:調用此函數,接收消息的視窗程式允許調用SendMessage的線程繼續運作,盡管接收消息的線程已傳回控制。調用ReplyMessage的線程也繼續運作。     如果消息不是通過SendMessage發送的,或者消息由同一個線程發送,ReplyMessage不起作用。    

BOOL InSendMessage(VOID);這個函數線上程處理線程間發送的消息時,傳回TRUE,而線上程處理線程内發送的或登記的消息時,傳回FALSE。InSendMessage和ReplyMessage的傳回值是一樣的。

DWORD InSendMessageEx(

  LPVOID lpReserved      // must be NULL

);

這個函數的傳回值指出正在處理的消息的類型。如果傳回值是ISMEX_NOSEND(定義為0),表示線程正在處理一個線程内發送的或登記的消息。如果傳回值不是ISMEX_NOSEND,就是表中描述的位标志的組合。

标志 描述
I S M E X _ S E N D 線程在處理一個線程間發送的消息,該消息是用S e n d M e s s a g e或Send Message Ti m e o u t函數發送的。如果沒有設定I S M E X _ R E P L I E D标志,發送線程被阻塞,等待應答
I S M E X _ N O T I F Y 線程在處理一個線程間發送的消息,該消息是用SendNotifyM e s s a g e函數發送的。發送線程不等待應答,也不會阻塞
I S M E X _ C A L L B A C K 線程在處理線程間發送的消息,該消息是用S e n d M e s s a g eC a l l b a c k發送的。發送線程不等待應答,也不會被阻塞
I S M E X _ R E P L I E D 線程在處理線程間發送的消息,并已經調用R e p l y M e s s a g e。發送線程不會被阻塞

喚醒一個線程:

當一個線程調用GetMessage或WaitMessage,但沒有對這個線程或這個線程所建立視窗的消息時,系統可以挂起這個線程,這樣就不再給它配置設定CPU時間。當有一個消息登記或發送到這個線程,系統要設定一個喚醒标志,指出現在要給這個線程配置設定CPU時間,以便處理消息。正常情況下,如果使用者不按鍵或移動滑鼠,就沒有消息發送給任何視窗。這意味着系統中大多數線程沒有被配置設定給CPU時間。

隊列狀态标志:

DWORD GetQueueStatus(

  UINT flags   // queue-status flags

);函數功能:查詢隊列的狀态:參數fuFlags是一個标志或一組由OR連接配接起來的标志,可用來測試特定的喚醒位。下表給出了各個标志取值及含義。

标志 隊列中的消息
Q S _ K E Y W M _ K E Y U P、W M _ K E Y D O W N、W M _ S Y S K E Y U P或W M _ S Y S K E Y D O W N
Q S _ M O U S E M O V E W M _ M O U S E M O V E
Q S _ M O U S E B U T TO N W M _ ? B U T TO N *(其中?代表L、M或R、*代表D O W N、U P或DBLCLK )
Q S _ M O U S E 同Q S _ M O U S E M O V E | Q S _ M O U S E B U T TO N
Q S _ I N P U T 同Q S _ M O U S E | Q S _ K E Y
Q S _ PA I N T W M _ PA I N T
Q S _ T I M E R W M _ T I M E R
Q S _ H O T K E Y W M _ H O T K E Y
Q S _ P O S T M E S S A G E 登記的消息(不同于硬體輸入事件)。當隊列在期望的消息過濾器範圍内沒有登記的消息時,這個标志要消除。除此之外,這個标志與Q S _ A L L P O S T M E S S A G E相同
Q S _ A L L P O S T M E S S A G E 登記的消息(不同于硬體輸入事件)。當隊列完全沒有登記的消息時(在任何消息過濾器範圍),該标志被清除。除此之外,該标志與Q S _ P O S T M E S S A G E相同
Q S _ A L L E V E N T S 同Q S _ I N P U T | Q S _ P O S T M E S S A G E | Q S _ T I M E R | Q S _ PA I N T | Q S _ H O T K E Y
Q S _ Q U I T 已調用P o s t Q u i t M e s s a g e。注意這個标志沒有公開,是以在Wi n U s e r.h 檔案中沒有。它由系統在内部使用
Q S _ S E N D M E S S A G E 由另一個線程發送的消息
Q S _ A L L I N P U T 同QS_ALLEVENTS|QS_SENDMESSAGE

 當調用GetQueueStatus函數時,fuFlags将隊列中要檢查的消息的類型告訴GetQueueStatus。用OR連接配接的QS_*辨別符的數量越少,調用執行的就越快。當GetQueueStatus傳回時,線程的隊列中目前消息的類型在傳回值的高字(兩位元組)中。傳回的低位字訓示自從上一次調用GetQueueStatus,GetMessage或PeekMessage以來加在隊列後并且未被處理的消息的類型。

不是所有的喚醒标志都由系統平等對待。對于QS_MOUSEMOVE标志,隻要隊列中存在一個未處理的WM_MOUSEMOVE消息,這個标志就要被設定。當GetMessage或PeekMessage (用PM_REMOVE)從隊列中放入新的WM_MOUSEMOVE消息之前,這個标志被關閉。QS_KEY、QS_MOUSEBUTTON和QS_HOTKEY 标志都根據相應的消息按與此相同的方式處理。

Q S_PAINT标志的處理與此不同。如果線程建立的一個視窗有無效的區域,QS_PAINT标志被設定。當這個線程建立的所有視窗所占據的區域變成無效時(通常由于對ValidateRect、Valid ateRegion或BeginPaint的調用而引起),QS_PAINT标志就被關閉。隻有當線程建立的所有視窗都無效時,這個标志才關閉。調用GetMessage或PeekMessage對這個喚醒标志沒有影響。

當線程的登記消息隊列中至少有一個消息時,QS_POSTMESSAGE标志就被設定。這不包括線程的虛拟輸入隊列中的硬體事件消息。當線程的登記消息隊列中的所有消息都已經處理,隊列變空時,這個标志被複位。

每當一個定時器(由線程所建立)報時(go off),QS_TIMER标志就被設定。在Get Message或P eekMessage傳回WM_TIMER事件之後,QS_TIMER标志被複位,直到定時器再次報時。

Q S _ SENDMESSAGE标志指出有一個消息線上程的發送消息隊中。系統在内部使用這個标志,用來确認和處理線程之間發送的消息。對于一個線程向自身發送的消息,不設定這個标志。雖然可以使用QS_SENDMESSAGE标志,但很少需要這樣做。

還有一個未公開的隊列狀态标志QS_QUIT。當一個線程調用PostQuitMessage時, Q S_ QUIT标志就被設定。系統并不實際向線程的消息隊列追加一個WM_QUIT消息。GetQueu eStatus函數也不傳回這個标志的狀态。

 從線程的隊列中提取消息的算法:當一個線程調用G e t M e s s a g e或P e e k M e s s a g e時,系統必須檢查線程的隊列狀态标志的情況,并确定應該處理哪個消息。

1) 如果QS_SENDMESSAGE标志被設定,系統向相應的視窗過程發送消息。GetMessage或PeekMessage函數在内部進行這種處理,并且在視窗過程處理完消息之後不傳回到線程,這些函數要等待其他要處理的消息。

2) 如果消息線上程的登記消息隊列中,函數GetMessage或PeekMessage填充傳遞給它們的MSG結構,然後函數傳回。這時,線程的消息循環通常調用DispatchMessage,讓相應的視窗過程來處理消息。

3) 如果Q S _ Q U I T标志被設定。G e t M e s s a g e或P e e k M e s s a g e傳回一個W M _ Q U I T消息(其中w P a r a m參數是規定的退出代碼)并複位Q S _ Q U I T标志。

4) 如果消息線上程的虛拟輸入隊列,函數GetMessage或PeekMessage傳回硬體輸入消息。

5) 如果Q S _ PA I N T标志被設定,GetMessage或PeekMessage為相應的視窗傳回一個WM-PAINT消息。

6) 如果QS_TIMER标志被設定,GetMessage或PeekMessage傳回一個WM_TIMER消息。

微軟在設計這個算法時有一個大前提,就是應用程式應該是使用者驅動的,使用者通過建立硬體輸入事件(鍵盤和滑鼠操作)來驅動應用程式。在使用應用程式時,使用者可能按一個滑鼠按鈕,引起一系列要發生的事件。應用程式通過向線程的消息隊列中登記消息使每個個别的事件發生。

是以如果按滑鼠按鈕,處理W M _ L B U T TO N D O W N消息的視窗可能向不同的視窗投送三個消息。由于是硬體事件引發三個軟體事件,是以系統要在讀取使用者的下一個硬體事件之前,先處理這些軟體事件。這也說明了為什麼登記消息隊列要在虛拟輸入隊列之前檢查。

BOOL TranslateMessage(

  CONST MSG *lpMsg   // address of structure with message

);這個函數檢查是否有一個WM_KEYDOWN或一個WM_ SYSKEYDOWN消息從輸入隊列中取出。如果有一個這樣的消息被取出,系統檢查虛鍵(virtual key)資訊是否能轉換成等價的字元。如果虛鍵資訊能夠轉換,TranslateMessage調用PostMessage将一個WM_CH AR消息或一個WM_SYSCHAR消息放置在登記消息隊列中。下次調用GetMessage時,系統首先檢查登記消息隊列中的内容,如果其中有消息存在,從隊列中取出消息并将其傳回。傳回的消息将是WM_CHAR消息或WM_SYSCHAR消息。再下一次調用GetMessage時,系統檢查登記消息隊列,發現隊列已空。系統再檢查輸入隊列,在其中找到WM_(SYS)KEYUP消息。GetMessage傳回這個消息。

下面的硬體事件序列W M _ K E Y D O W N、W M _ K E Y U P生成下面的到視窗過程的消息序列(假定虛鍵資訊可以轉換成等價的字元):WM_KEYDOWN WM_CHAR WM_KEYUP。

在系統檢查了登記消息隊列之後,但尚未檢查虛拟輸入隊列時,它要檢查Q S _ Q U I T标志。我們知道,當線程調用P o s t Q u i t M e s s a g e時設定Q S _ Q U I T标志。調用P o s t Q u i 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 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設定一個标志,而不是将W M _ Q U I T消息放入消息隊列中?有兩個理由。第一,在低記憶體(low memory)情況下,登記一個消息有可能失敗。如果一個程式想退出,它應該被允許退出,即使是在低記憶體的情況下。第二個理由是使用标志可使線程線上程的消息循環結束前完成對所有其他登記消息的處理。

注意要記住G e t M e s s a g e或P e e k 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函數導緻一個線程睡眠,直到該線程需要處理一個與使用者界面(UI)相關的任務。一個線程可以調用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 MsgWaitForMultipleObjects(

  DWORD nCount,          // number of handles in the handle array   LPHANDLE pHandles,     // pointer to the object-handle array

  BOOL fWaitAll,         // wait for all or wait for one

  DWORD dwMilliseconds,  // time-out interval in milliseconds

  DWORD dwWakeMask       // type of input events to wait for

);

DWORD MsgWaitForMultipleObjectsEx(

  DWORD nCount,          // number of handles in handle array

  LPHANDLE pHandles,     // pointer to an object-handle array

  DWORD dwMilliseconds,  // time-out interval in milliseconds

  DWORD dwWakeMask,      // type of input events to wait for

  DWORD dwFlags          // wait flags

);

當一個核心對象變成有信号狀态( s i g n a l e d)或當一個視窗消息需要派送到調用線程建立的視窗時,這兩個函數用于線程排程。在内部,系統隻是向核心句柄的數組添加一個事件核心對象。d w Wa k e M a s k參數告訴系統何時讓事件成為有信号狀态。d w Wa k e M a s k參數的可能取值的合法範圍與可傳遞到G e t Q u e u eS t a t u s函數的參數值一樣。

正常情況下,當Wa i t F o r M u l t i p l e O b j e c t s函數傳回時,它傳回變成有信号狀态的對象的索引以滿足調用(WA I T _ O B J E C T _ O到WA I T _ O B J E C T _ O + n C o u n t-1)。增加d w Wa k e M a s k參數就如同向調用增加又一個句柄。如果由于喚醒掩碼, 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 )被滿足,傳回值将是WA I T _ O B J E C T _ O+n C o u n t。

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參數引進的。對這個參數,可以指定下面标志的任意組合。

标志 描述
M W M O _ WA I TA L L 函數等待所有要變成有信号狀态的核心對象及要出現線上程隊列中的特定消息。如果沒有這個标志,函數等待直到有一個核心對象變成s i g n a l e d,或指定的消息出現線上程的隊列中
M W M O _ A L E RTA B L E 函數在一個可報警狀态等待
M W M O _ I N P U TAVA I L A B L E 當任何指定的消息線上程的隊列中時

如果不想要任何這些附加的特性,可對參數d w F l a g s傳遞零(0)。

備注:由于這個函數隻是向核心句柄的數組增加一個内部事件核心對象, 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時,那麼當所有核心對象成為有信号狀态,并且指定的消息類型出現線上程的隊列中時,函數傳回。

當調用這兩個函數時,實際是檢視是否有指定類型的新消息被放入調用線程的隊列。

通過消息發送資料:W M _ S E T T E X T消息的處理。當調用S e n d M e s s a g e時,函數中的代碼要檢查是否要發送一個W M _ S E T T E X T消息。如果是,就将以零結尾的字元串從調用程序的位址空間放入到一個記憶體映像檔案中,該記憶體映像檔案可在程序間共享。然後再發送消息到其他程序的線程。當接收線程已準備好處理W M _ S E T T E X T消息時,它在自己的位址空間中确定包含新的視窗文本标題的共享記憶體映像檔案的位置,再将W M _ S E T T E X T消息派送到相應的視窗過程。在處理完消息之後,記憶體映像檔案被删除。幸而大多數消息不要求這種類型的處理。僅當這種消息是程式在程序間發送的消息,特别是消息的w P a r a m或l P a r a m參數表示一個指向資料結構的指針時,要做這樣的處理。

 關于W M _ G E T T E X T消息的處理:char szBuf[200];SendMessage(FindWindow(NULL, "Calculator"), WM_GETTEXT, sizeof(szBuf), (LPARAM) szBuf);W M _ G E T T E X T消息請求C a l c u l a t o r的視窗過程用該視窗的标題填充s z B u f所指定的緩沖區。當一個程序向另一個程序的視窗發送這個消息時,系統實際上必須發送兩個消息。首先,系統要向那個視窗發送一個W M _ G E T T E X T L E N G T H消息。視窗過程通過傳回視窗标題的字元數來響應這個消息。系統利用這個數字來建立一個記憶體映像檔案,用于在兩個程序之間共享。當記憶體映像檔案被建立時,系統就發送消息來填充它。然後系統再轉回到最初調用S e n d M e s s a g e的程序,從共享記憶體映像檔案中将資料複制到s z B u f所指定的緩沖區中,然後從S e n d M e s s a g e調用傳回。

要建立自己的(W M _ U S E R+x)消息,并從一個程序向另一個程序的視窗發送,那又會怎麼樣?為此,微軟建立了一個特殊的視窗消息,W M _ C O P Y D ATA以解決這個問題。COPYDATASTRUCT cds;

SendMessage(hwndReceiver, 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;//規定了向另外的程序發送的位元組數

   PVOID lpData;//指向要發送的第一個位元組,所指向的位址,當然在發送程序的位址空間中。

} COPYDATASTRUCT:當一個程序要向另一個程序的視窗發送一些資料時,必須先初始化C O P Y D ATA S T R U C T結構。當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成員指向接收程序位址空間中的共享記憶體映像檔案的視圖。

關于W M _ C O P Y D ATA消息,應該注意三個重要問題:

隻能發送這個消息,不能登記這個消息。不能登記一個W M _ C O P Y D ATA消息,因為在接收消息的視窗過程處理完消息之後,系統必須釋放記憶體映像檔案。如果登記這個消息,系統不知道這個消息何時被處理,是以也不能釋放複制的記憶體塊。

系統從另外的程序的位址空間中複制資料要花費一些時間。是以不應該讓發送程式中運作的其他線程修改這個記憶體塊,直到S e n d M e s s a g e調用傳回。

用W M _ C O P Y D ATA消息,可以實作1 6位和3 2位之間的通信。它也能實作3 2位與6 4位之間的通信。這是使新程式同舊程式交流的便捷方法。

繼續閱讀