要更好地使用C++進行Windows編程,就須要進一步瞭解其消息機制。在Windows應用程序中,事件驅動是圍繞着消息的產生和處理展開的,消息是對發生的事件的描述信息。消息通知程序有關事件的發生。一條消息包含有消息的名字、標識、消息發生時的一些參數,以及處理這條消息的函數入口指針。每當用戶進行某種操做,好比鼠標單擊或鍵盤按鍵,就會觸發相應的事件。而事件是以消息的方式通知Windows應用程序的。一旦應用程序得到某條消息,就根據消息映射表查找相應消息的響應函數的入口地址,調用該函數處理消息,完成用戶預期的功能。linux
在Windows操做系統中,應用程序主要以窗口的形式存在。窗口是一個可視的人機交互界面,用來接收各類事件,如用戶鍵盤/鼠標事件、外設的請求事件、定時器的請求事件、信號量的請求事件等。所以,它也就成爲應用程序控制消息的發送端和接收端。即Windows應用程序是圍繞窗口進行的,窗口不只提供了可視化的應用程序的界面,也是Windows消息的產生和響應的地方。算法
消息的產生是因爲相應的事件被觸發;消息的發送以隊列形式進行;消息響應遵循必定的順序。MFC類庫爲這種消息響應機制提供了完整的處理功能。MFC類庫中的不少類都具備處理相應消息的功能。在面向過程的程序設計方式中,對外設,好比鼠標、鍵盤等的控制是經過輪詢方式進行,即分別定時查詢這些設備的輸入請求來完成的。而在Windows環境中,這些控制是經過消息機制完成的。所以,Windows也被稱爲「基於事件驅動的、消息機制的」操做系統。消息機制是Windows能進行多任務併發處理的基礎,它保證了Windows下同時運行的程序可以協同做業。編程
在Windows中,應用程序都包含一個消息循環。該消息循環持續反覆檢測消息隊列,查看是否有用戶事件消息,這些用戶事件消息包括鼠標移動、單擊、雙擊、鍵盤操做和計時器到達等。事實上,這些事件首先被Windows系統接收到。當Windows接收到這些事件後,會產生一些相應的描述事件的消息,而且將這些消息分發到相應的應用程序。應用程序接到這些消息後,根據不一樣的消息查詢消息映射,調用其相應的消息響應函數,完成必定的功能與過程。這一系列動做稱之爲消息響應。windows
調用(calling)機制從彙編時代起已經大量使用:準備一段現成的代碼,調用者能夠隨時跳轉至此段代碼的起始地址,執行完後再返回跳轉時的後續地址。CPU爲此準備了現成的調用指令,調用時能夠壓棧保護現場,調用結束後從堆棧中彈出現場地址,以便自動返回。借堆棧保護現場真是一項絕妙的發明,它使調用者和被調者能夠互不相識,因而纔有了後來的函數和組件,使吾輩編程者如此輕鬆愉快。安全
調用機制並不是完美。回調函數就是一例。函數之類本是爲調用者準備的美餐,其烹製者應對食客瞭如指掌,但實情並不是如此。例如,寫一個快速排序函數供他人調用,其中必包含比較大小。麻煩來了:此時並不知要比較的是何類數據--整數、浮點數、字符串?因而只好爲每類數據製做一個不一樣的排序函數。 更通行的辦法是在函數參數中列一個回調函數地址,並通知調用者:君需本身準備一個比較函數,其中包含兩個指針類參數,函數要比較此二指針所指數據之大小,並由函數返回值說明比較結果。排序函數借此調用者提供的函數來比較大小,借指針傳遞參數,能夠全然無論所比較的數據類型。被調用者回頭調用調用者的函數(夠咬嘴的),故稱其爲回調(callback)。服務器
回調函數使程序結構亂了許多。Windows API函數集中有很多回調函數,儘管有詳盡說明,仍使初學者一頭霧水。恐怕這也是無奈之舉。不管何種事物,能以樹形結構單向描述畢竟讓人舒服些。若是某家族中孫輩又是某祖輩的祖輩,恐怕無人能理清其中的頭緒。但數據處理之複雜每每須要構成網狀結構,非簡單的客戶/服務器關係能窮盡。多線程
Windows系統還包含着另外一種更爲普遍的回調機制,即消息機制。消息本是Windows的基本控制手段,乍看與函數調用無關,實際上是一種變相的函數調用。發送消息的目的是通知收方運行一段預先準備好的代碼,至關於調用一個函數。消息所附帶的WParam和LParam至關於函數的參數,只不過比普通參數更通用一些。應用程序能夠主動發送消息,更多狀況下是坐等Windows發送消息。一旦消息進入所屬消息隊列,便檢感興趣的那些,跳轉去執行相應的消息處理代碼。操做系統本是爲應用程序服務,由應用程序來調用。而應用程序一旦啓動,卻要反過來等待操做系統的調用。這分明也是一種回調,或者說是一種廣義回調。併發
其實,應用程序之間也能夠造成這種回調。假如進程B收到進程A發來的消息,啓動了一段代碼,其中又向進程A發送消息,這就造成了回調。 這種回調比較隱蔽,弄很差會搞成遞歸調用,若缺乏終止條件,將會循環不已,直至把程序搞垮。如果故意編寫成此遞歸調用,並設好終止條件,卻是頗有意思。但這種程序結構太隱蔽,除非十分必要,仍是不用爲好。異步
利用消息也能夠構成狹義回調。上面所舉排序函數一例,能夠把回調函數地址換成窗口handle。如此,當須要比較數據大小時,不是去調用回調函數,而是借API函數SendMessage向指定窗口發送消息。收到消息方負責比較數據大小,把比較結果經過消息自己的返回值傳給消息發送方。所實現的功能與回調函數並沒有不一樣。固然,此例中改成消息純屬畫蛇添腳,反倒把程序搞得很慢。但其餘狀況下並不是老是如此,特別是須要異步調用時,發送消息是一種不錯的選擇。假如回調函數中包含文件處理之類的低速處理,調用方等不得,須要把同步調用改成異步調用,去啓動一個單獨的線程,而後立刻執行後續代碼,其他的事讓線程慢慢去作。一個替代辦法是借API函數PostMessage發送一個異步消息,而後當即執行後續代碼。這要比本身搞個線程省事許多,並且更安全。函數
現在咱們是活在一個object時代。只要與編程有關,不管何事都離不開object。但object並未消除回調,反而把它發揚光大,弄獲得處都是,只不過大都以事件(event)的身份出現,鑲嵌在某個結構之中,顯得更正統,更容易被人接受。應用程序要使用某個構件,總要先弄清構件的屬性、方法和事件,而後給構件屬性賦值,在適當的時候調用適當的構件方法,還要給事件編寫*處理例程,以備構件代碼來調用。何謂事件?它不過是一個指向事件例程的地址,與回調函數地址沒什麼區別。
不過,此種回調方式比傳統回調函數要高明許多。首先,它把讓人不太舒服的回調函數變成一種天然而然的處理例程,使編程者頓覺氣順。再者,地址是一個危險的東西,用好了可以使程序加速,用很差到處是陷阱,程序隨時都會崩潰。現代編程方式老是想法把地址隱藏起來(隱藏比較完全的如 VB 和 Java),其代價是下降了程序效率。事件例程使編程者無需直接操做地址,但並不會使程序減速。更妙的是,此一改變,本是有損程序結構之奇技怪巧變成一種嶄新設計理念,不只免去被人抨擊,並且逼得吾等凡人淨手更衣,細細研讀,仰慕至今。
事件與消息的概念在計算機中較易混淆,但本質不一樣:
事件由用戶觸發且只能由用戶觸發,操做系統可以感受到由用戶觸發的事件,並將此事件轉換爲一個特定的消息發送到程序的消息隊列中。
這裏強調的是
- 能夠說「用戶觸發了一個事件」,而不能說「用戶觸發了一個消息」。
- 用戶只能觸發事件,而事件只能由用戶觸發。
一個事件產生後,將被操做系統轉換爲一個消息,因此一個消息多是由一個事件轉換而來或者由操做系統產生。
總結事件和消息的來源
事件:只能由用戶經過外設的輸入產生。
消息:產生消息的來源有三個
(1) 由操做系統產生。
(2) 由用戶觸發的事件轉換而來。
(3) 由另外一個消息產生。
注: 上述描述彷佛不太嚴謹,如定時器事件應該算是內部事件,並不是用戶產生。
程序是數據的流動。全部的編程技術(tricks)都在往兩個方面去努力:
簡單記錄本身對於 消息驅動 和 事件驅動的理解。
關於這兩者的具體區別,於實現上來講,兩者都是 註冊綁定,而後交付執行。
消息驅動模型在註冊的時候僅僅註冊一個回調函數做爲處理函數。
而事件驅動模型則須要註冊多個函數做爲處理函數。
消息驅動模型因爲處理函數只有一個的緣故,
故須要在回調函數中使用switch
等手段,
對消息進行派發並具體處理。
而事件驅動模型則須要在各個回調函數中處理各自的事物。
因此從設計角度說, 消息驅動模型的複用性高於事件驅動模型, 或者說事件驅動模型通常用於處理某個特定的問題。 而形成這種情形的緣由是, 消息驅動模型不須要知道具體的消息含義, 而事件驅動模型則須要知道具體的事件含義,不然沒法經過回調函數處理。