[轉載] MFC技術內幕簡結

在學習新知識時,我個人比較喜歡用聯想、比較和總結的方法去思考問題,解決問題,使一切未知的與已知的相聯繫,使一切已知的相似的相比較,從而總結他們的共性,整理與理清腦中亂糟糟的知識,從而達到提升。學習編程也不例外,在學編程過程中,我發現編程技術中有一種非常非常常用的技術:模型!消息機制、文檔視圖結構、動態生成以及COM都使用了一種相似的模型方法去解決問題。以下請聽小弟一一分析學習過程中的心得總結:

消息機制:

Windows程序的進行是依靠外部發生的事件來驅動,操作系統中有一個USER模塊專門用來捕捉外圍設備所發生的事件;比如當按下鼠標時,USER模塊就捕捉到一個鼠標消息,而且依據當時系統界面的情況確定響應此消息的窗口,填充消息結構MSG,並將這個鼠標消息放入系統消息隊列中,GetMessage函數從消息隊列中取得一個消息,並依據消息的內容把消息發往特定的窗口函數,由窗口函數負責處理。每一條消息,都對應着一組代碼段,程序通過執行代碼段來完成我們的任務;在MFC中,用類的數據成員封裝了我們要操作的數據,用函數成員封裝了要執行的代碼段;於是一條消息通常都對應一個函數。那麼,消息怎麼知道自己該由那一個類中的那一個函數成員來執行呢?

首先,MFC爲每一個能處理消息的類設計了一個消息映射表Message MAP,消息映射表負責把這個類能處理的消息和其處理函數“綁”在一塊。其次,我們把有繼承關係的類的消息映射錶鏈接起來形成一張消息網,通過依次比較有繼承關係的各個類中映射表中的內容(MessageID)尋找與消息對應的處理函數,如果找到就調用其處理函數。如果在這張消息網中找不到相等的MessageID(網中的類中都沒有定義響應這個消息的函數),即分兩種情況處理:如果是命令消息或由其它控件發送過來的消息,就跳到另外一張消息網看看有沒有符合的選擇,跳完了專家們設定的該跳的網後仍然找不到符合的消息,則調用系統默認的處理消息的函數,找到了當然調用其處理函數了;如果是Windows消息,則不用跳了,調用系統默認處理函數就行了。由此可見,在消息傳遞的過程中,我們需要知道一些信息:這個消息是什麼類型以及如果要“跳網”時應該先跳到那一張網,跳到那一張網後仍然找不到後才放棄。因此,在有處理消息能力的類的函數中定義了一個名爲OnWndMsg函數分辨消息的類型,定義了一個名爲OnCmdMsg函數來決定跳網的路線(這條路線由專家們事先設定好了,無需我們勞神)。

剛纔我們說,把消息與其處理函數“綁”在一塊,把有繼承關係的類的消息映射網鏈接起來,在MFC中我們是怎麼辦到的呢?MFC中設定了幾個宏來負責有關工作:DECLARE_MESSAGE_MAP/BEGIN_MESSAGE_MAP/ON_COMMAND/END_MESSAGE_MAP。基中DECLARE_MESSAGE_MAP這個宏裏主要包含了一個消息映射表結構成員,一個獲取消息映射表結構地址的函數及一個messageEntris成員。而BEGIN_MESSAGE_MAP/ON_COMMAND/END_MESSAGE_MAP三個宏負責填充消息映射表的內容並依類的繼承關係建立一張消息網。於是,當我們向類中加入一個新消息,我們就不必在類申明中加入任何的東西了,因爲DECLARE_MESSAGE_MAP裏有一個獲取消息映射表結構地址的函數,我們就只需要向這個地址加入新的消息項和其處理函數名就行了。爲類定義一個新消息就變成了做填空題。我把這種消息與函數間的結構理解成一種模型;消息與函數之間原本應該直接對應的,現在我們爲了方便管理方便操作,人爲地把它們分開,加入中間層(消息映射表,專門管理消息與處理函數的對應關係),以達到更強大的功能。這是一模型,間接模型,也是一種方法,即把A—C模型變成A—B—C模型。我也把做這種填空題看成一種模板,就好比畫龍點睛,龍已經畫好,我們只需點睛,龍就會飛了。以後我們會發現,在計算機技術中,這是一種常用的解決問題的方法。

動態生成與類型識別:

無論是執行期類型識別,動態生成還是檔案的讀寫,都是用同一數據結構CRuntimeClass同一張稱爲類別型錄網的網。類別型錄網是由CRuntimeClass鏈接而成的,在類中的申明由DECLARE_DYNAMIC宏封裝,鏈接工作由類外的IMPLEMENT_DYNAMIC宏封裝完成,當然根據不同的應用,CRuntimClass有不同的內容,所用的宏的名字也略有不同。他的工作原理和消息機制有點相似,都是運用“指針”這一大特性完成橋樑工作,也都利用指針在類外另構一張網,集中管理信息。這張網有什麼大用途呢?是這樣的,當我們在執行期要生成一個類對象時(比如在讀檔案時,裏面的數據要根據實際情況產生相應的對象我們才能讀),我們需要的信息:類的名字,類的大小,類的建構函數,類與其它類的關係等等有關類的信息,都將記錄在類別型錄網上;那麼當我們在執行期獲得一個(用字符串表示的)類名時,我們就可以在這張網上找出對應的元素(通過比較網中成員的類名),然後調用其建構函數(裏面有個指針指向其建構函數),產生出對象。當然啦,執行期所產生的對象相應的類必需要在類別型錄網中才行,而這張網是由你一手一腳用IMPLEMENT_DYNAMIC建立起來的,裏面有些什麼類,你一清二楚。至於在如何建網,比較麻煩,反正有那兩個宏自動幫我們完成這些工作,我們只需要做做填空題,確定把那些類掛在網上即可。大家回頭想想,產生對象這件事,原本很簡單的,原本我們可以直接產生的,可是爲了把程序活躍起來,爲了在執行期也能動態生成(那時我們可不能像以前那隨隨便便加個語句就生成一個對象了,因爲我們不可能再加語句到已經分發的軟件上去),於是我們加了箇中間層,我們生成了一張網來集中管理類型信息,這種解決問題的辦法是不是很高明!?我把動態生成的過程理解成一種模型,間接模型,也是一種方法,即把A—C模型變成A—B—C模型。我也把做這種填空題看成一種模板,就好比畫龍點睛,龍已經畫好,我們只需點睛,龍就會飛了。這一切都是爲了爲程序員提供了方便。

文檔/視圖結構:

我們把文檔看成一種數據,而視圖就是把這些數據按照我們想要的方式展示給我們看的窗口。如果一份數據只以一種方式顯示,如果我們只需顯示一種文件類型中的數據,那麼,數據與視圖一一對應也不需要玩什麼花招就能滿足我們的要求的。可是更多的時候,我們需要把數據以不同的方式(比如有關統計的數據,我們然望有線形圖,柱形圖,文字等多種方式顯示給我們看)顯示出來,我們更希望在同一程序中能處理數種不同文件類型中的數據併爲之提供不同的顯示界面,那麼一一對應好像就變得行不通了!怎麼辦?呵呵,我們加多一箇中間層就行了!這個中間層在MFC中叫作Document Template,由它來負責集中管理文檔視圖之間的轉換,嗯,應該說是管理Document/View/Frame,Frame是包在View外面的窗口,他可以爲View/Document提供專用的菜單。爲什麼數據能以不多的形式顯示呢?那是因爲,據然數據能以一種形式顯示,就必定能以多種形式顯示!呵呵,好像有點強辭奪理,其實呢,因爲有了箇中間層把文檔視圖分開,那麼只要你按原數據多設計幾個顯示形式就行了,你需要什麼形式顯示,只要告訴Document Template,它就會體貼地幫你安排好(接上你想要的視圖形式,和此視圖專用的Frame)要做的工作了。同理,在同一主框架中爲不同文件類型中的數據提供專用的View和Frame也由 Document Template這個中界幫我們安排。理所當然的,在Document/View/Frame三位一體結構中,有一個鏈表把文件類型(一個Document Template對應管理一種類型)連接起來,在顯示文件中數據時也有個查找過程,即先找到要顯示的文件類型,再查找要顯示View方式,之後動態生成專用的View/Frame。唉,裏面真是很複雜啊,想想都頭暈,不管啦,反正都是那些天才們的事。呵呵,不過我也把文檔視/圖結構理解一種模型,也是一種方法,即把A—C模型變成A—B—C模型。

C++對象模型:

按我的理解,C++對象模型就是爲了實現C++中面向對象程序設計的有關性質設計出來的有關類的數據成員、函數成員怎樣在內存中安排(才能更高效地實現面向對象的性質)的一種類的模型。爲了實現多態,對象模型便不得不多設計了一張virtual table表,而且這張表要考慮單繼承多繼承所帶來的問題,還要考慮指向這張表的指針地址放在爲對象分配得來的內存(以二進制形式順序存放對象成員)的什麼位置才理想,派生類特別是有多個基類的派生類怎麼“繼承”這張虛表,同時也要考慮怎麼“繼承”基類的其它成員(很多時候,派生類都要複製所有基類的所有成員,因此而顯得浪費內存,確也無可奈何,所以,如果可以最好儘可能少派生類,並且一定要從最“基”的類派生)。也因爲要實現多態,繼承,封裝等等性質,類的構造函數、析構函數,也變得複雜起來了,要考慮很多的問題,因爲類與類之間不再獨立了,他們之間一但有聯繫,事情也複雜起來了。類的函數成員,數據成員,在這種情況下,日子也不好過了,他們在內存中的安排也就要求有更高的技巧性了。基本上,你可以從觀察C++對象模型中,知道爲什麼C++會有多態,繼承這些特性,也知道C++語法爲什麼是這樣子的,有什麼限制等等。在學《深度探索C++對象模型》這本書的時候,我有這樣的感覺:整本書各章間基本上是千絲萬縷,各個章節要求你理解的道理都差不多,都要回歸到內存分配這個話題上;所以,我建議,畫兩張有四五個類的詳細類圖,裏面包含有類的各種關係,那麼當我們看着這張圖來學這本書的時候就會變得輕鬆多了,雖然裏面也有圖,但有點亂,因此我畫了兩張類圖,以求精簡完整。

COM與註冊表:

COM接口是什麼?按我現在的理解是,如我先前所說的模型中,把A—C模型變成A—B—C模型中的B中間層,A和C都是相對獨立的可執行程序,它是一種協議一種規則,用於把A和C連繫起來,爲他們通信提供方便。COM接口定義了一組虛函數,這些函數的功能都將在A(或B)中得以實現;接口也是類結構並且還是A(或B)的父類,根據C++多態性,我們知道,通過父類就可以調用派生類的函數(只要指向的是派生對象的地址)了。現在問題是,怎麼獲取派生類的對象地址呢?我並不清楚。因爲C++擁有多繼承這一偉大性質,所以我們可以爲A定義任義多個COM接口,利用嵌套類技術把COM接口的派生類插入A中以實現連接;比較複雜。

到目前爲止,我仍然不能很好地理解註冊表是什麼東東,計算機怎麼管理內存的;我看過不少那些號稱是內幕技術能夠知其所以然的書,但一講到這兩方面都是輕輕一筆帶過,於是我只能靠猜了。表面上看來,註冊表是一些符號串的集合,而這些符號聽說是記錄應用程序中的一些配置信息,應用程序需要記錄一些什麼樣的信息呢?再讓我想想,我們使用程序時,好像新啓動的程序界面和上一次最後一次使用關閉時界面好像是一樣的(大小,位置,顏色等),爲什麼會這樣子呢?莫非是註冊表裏記錄差這些信息,每次啓動程序時都從註冊表裏取得這些信息?我懷疑是,還沒得到證實。學COM的時候,書上好像也說過可執行程序文件的路徑也記錄在註冊表裏。我對內存管理很好奇,但對他的理解僅限於在學校課本里學到的,那遠遠不能滿足我的求知慾,以後有空必定再學。

Application Framework:

剛開始學VC時,就常聽到有人說MFC就是類庫,對API進行了簡單的封裝;呵呵,果真如此嗎?真是簡單的封裝嗎?不!至少最重要最關鍵的幾個類不是!MFC中幾個最最重要的類,如CWinApp,CView, CFrameWnd,CDocument,CDocTemplate等;絕不僅僅是“簡單的封裝”!它們之互相合作,通過消息的流動而溝通,並且互相調用對方的函數,等等,互相調用函數?說起來好像挺不錯的,但做起來呢,要它們幾個類在互相調用而顯得亂七八糟的情況仍能條理清淅,共同完成幾乎我們所開發的所有的程序的基本框架,要考慮的問題何其的多!?Application Framework-----程序的基本框架?是的,我的理解就是這樣,Application Framework核心思想就基於面象對向開發程序的思想,爲我們做出一套完美的基本程序模型,我們所開發的程序都以它爲基礎。在面象對向語言C++之下,我們只需要從模型中原有的類中派生出自己的類,改寫一些虛函數,定義一些新函數,就可以方便地完成我們想要的功能了,很多麻煩的事(如處理Document/View/Frame中先前我們討論的麻煩事,程序初始化等)都由基本程序模型幫我們解決掉了。

應用程序與用戶的交互:

固執的我一直想從C/C++語言出發去理解應用程序與用戶之間的交互的問題;在我腦裏常常想到的是程序裏有一個main(),裏面的程序代碼是順序地執行的;於是應用程序可以同時甚至交錯地運行程序中不同地段的代碼的問題就常常令我頭痛。現在,在我看了不少源碼後,我覺悟了,原來,我們所開發出的程序,除去啓動程序的初始化操作中要順序執行的代碼外,還有很多死的代碼段,它們並沒有執行起來,和死的東西談判順序執行,我覺得自己很笨。不過,死是相對的,操作系統爲我們激活這些代碼段提供了一線光明:向程序發送消息。消息在操作系統的幫助下,送到應用程序中,應用程序就順序執行一系列死的代碼,包括調用執行消息所對應的函數,這時談順序執行纔有意義。我們所發送的消息,都必須要有相應處理函數,這樣消息纔會有意義;各消息的處理函數之間,程序員應該儘可能讓他們獨立起來,辦完自己的事就好了,函數執行完了就完了,不要與其它消息處理函數扯上太多的關係,這就是模塊化思想。程序功能模塊與功能模塊之間大談程序順序執行是沒意義的。而現在我還知道了順序執行的本質並沒有改變,只是因爲有了個While循環用於捕捉消息纔會變成現在這個看起來有點不可思議的樣子。
我常常刻意地追求知其所以然,可事實往往與願違。花太多的時間在這些理論上,而忽視了實踐,就變成了膚淺;最可恨的是,以前辛苦追尋源代碼而取得的收穫,不僅在實踐運用中沒什麼表現,而且還淡忘了;在發現API本身就是個大黑箱時,淚流滿面,到頭來,還是要查手冊,還是要清楚瞭解API函數參數及功能才能夠運用;使用API和使用那些控件一樣,不知它在裏面搞什麼鬼。暗箱操作,我們無能迴天,我們所能做的,也就只有儘可能詳細地瞭解有關接口與參數問題了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章