面試sendmessage與postmessage前篇
SendMessage 與 PostMessage的討論?
相關資料 Windows程式設計第五版 - P60 - 3.2.2 - 佇列訊息和非佇列訊息
前面提過Windows將訊息傳送給一個視窗,意思是說Windows呼叫了該視窗的視窗過程。但是,一個Windows程式同時還有一個訊息迴圈用於從訊息佇列中檢索和分發訊息,其中檢索訊息是通過呼叫GetMessage實現的,而分發訊息是通過呼叫DispatchMessage而實現的。
那麼,是Windows程式捕獲到訊息(非常類似於字元模式程式對鍵盤輸入的捕獲)然後將這些訊息轉送到某個目的地呢?還是直接從程式外部接收訊息呢?答案是兩者兼有。
訊息既可以是“佇列訊息”,也可以是“非佇列訊息”。佇列訊息是指那些由Windows放入程式的訊息佇列中的訊息。在程式的訊息迴圈中,訊息被檢索,然後被投遞到視窗過程中。非佇列訊息則是由Windows對視窗過程的直接呼叫而產生的。我們一般說佇列訊息被“投遞”(post)到訊息佇列中,而非佇列訊息則是被“傳送”(send)到視窗過程。無論是在哪種情形下,視窗過程都會為視窗獲取所有訊息——無論是佇列訊息還是非佇列訊息。因此,視窗過程實際上是視窗的“訊息中心”。
佇列訊息主要是由使用者的輸入產生,主要形式為按鍵訊息、由按鍵產生的字元訊息、滑鼠移動、滑鼠單擊等。此外,佇列訊息還包括定時器訊息、重繪訊息和退出訊息。
非佇列訊息則包括佇列訊息以外的其他所有訊息。非佇列訊息通常由呼叫特定的Window函式引起。例如,當WinMain呼叫CreateWindow函式時,Windows就會建立視窗,並在建立過程中向視窗過程傳送一條WM_CREATE訊息。當WinMain呼叫ShowWindow函式時,Windows又會將WM_SIZE訊息和WM_SHOWWINDOW訊息傳送給視窗過程。接下來,WinMain又對UpdateWindow進行了呼叫,這就促使Windows向視窗過程傳送一條WM_PAINT訊息。表明鍵盤或滑鼠輸入的佇列訊息也能夠產生非佇列訊息。例如,當用鍵盤或滑鼠選擇某個選單項時,鍵盤或滑鼠訊息會進入訊息佇列,而最終表明有選單項被選中的WM_COMMAND訊息卻是一個非佇列訊息。
這個過程顯然十分複雜。但幸運的是,這些複雜性大部分都由Windows承擔了。從視窗過程的視角看,這些訊息是以有序、同步的方式到來的。視窗過程可以選擇對這些訊息進行某種處理或乾脆直接忽略掉。
在我剛才提到訊息是以有序、同步的方式到來時,這句話的第一層含義是指訊息與硬體中斷不同。在視窗過程處理某一訊息的過程中,程式不會被其他訊息突然中斷。
雖然Windows程式可以有多個執行執行緒,但每個執行緒的訊息佇列僅為那些其視窗過程在該執行緒內執行的視窗進行訊息處理。換言之,訊息迴圈和視窗過程不是併發執行的。當一個訊息迴圈從其自身的訊息佇列中檢索訊息,並呼叫DispatchMessage函式將檢索到的訊息傳送給視窗過程時,只有在視窗過程將控制權返還給Windows後,DispatchMessage才會返回。
但是,視窗過程可以呼叫為其傳送其他訊息的函式。這種情形下,在該函式呼叫返回之前,視窗過程必須將第二個訊息處理完畢,此時視窗過程才處理前一條訊息。例如,當一個視窗過程呼叫UpdateWindow時,Windows會以一條WM_PAINT訊息來呼叫視窗過程。當視窗過程處理完WM_PAINT訊息後,UpdateWindow呼叫才將控制權返還給視窗過程。
這就意味著視窗過程必須是可重入的。在大多數情形下,這並不會帶來什麼問題,但是對此必須做到心中有數。例如,假定在視窗過程處理某條訊息期間,你對一個靜態變數進行了設定,接著又呼叫了一個Windows函式。當該函式返回時,你能確保這個變數仍然跟先前一樣嗎?我們的確無法保證這一點,因為如果你所呼叫的特定Windows函式產生了另外一條訊息,且視窗過程在處理第二條訊息期間對該變數進行了修改,則該變數的狀態一定會發生改變。而這也是我們在編譯Windows程式時需要將某些編譯優化關閉的原因之一。
在許多情況下,視窗過程必須保留其從訊息中獲取的資訊,並在處理其他訊息時使用該資訊。這種資訊必須儲存在視窗過程所定義的靜態變數中,或儲存在全域性變數中。
原文完。
個人補充: sendmessage,不進入訊息佇列。 postmessage,進入訊息佇列,由GetMessage獲取。