謹(jǐn)以此文祭奠過去多少年下來,日日夜夜學(xué)習(xí)MFC源代碼的寶貴時光。
曾經(jīng)以為MFC是世界上最神圣無比的東西,發(fā)誓在沒徹底弄透MFC之前,絕不越雷池半步,碰都不要去碰LISP、ERLANG、PYTHON、VCL、ATL、STL、LOKI、BOOST各種美妙的好東西。即使一再聽到高人說MFC設(shè)計得不好,非常糟糕,也都至死而不悔,然后只要稍微聽到有人在說MFC的偉大,心里就特別高興。買了各種各樣的VC方面的書,什么四大天王、什么幾十百千例、什么VC項目實例,徹夜苦讀,單步調(diào)試,一次又一次地徘徊在MFC套來套去的各種美妙代碼之中,觀察里面各個對象的值。一次又一次地感嘆MFC的偉大,一直在想,這輩子,如果能徹底地掌握MFC里面的一切秘密,朕就心滿意足了。終于有一天,終于徹底醒悟,原來MFC真不是好東西,不過就是那些伎倆而已,真不該將它看得如此的偉大。這種念頭一起來,再回過頭來看MFC的一坨一坨代碼,只覺得清晰無比,又感覺代碼之中充滿了各種無奈。接著再看看那些VC方面的書,除了四大天王還稍微有些可取之處,其他的任何一本,奇臭無比,沒有一行代碼經(jīng)得起推敲, 文筆也糟糕無比,其內(nèi)容又一再重復(fù),抄來抄去,真正是一本又一本的狗屎。 感嘆一下:這些書犧牲了多少寶貴的樹木,又折磨死了多少讀者的腦細(xì)胞,實在罪大惡極。
在MFC大河日下的當(dāng)今的大環(huán)境之下,在下也不想對它落井添石,畢竟也在其上學(xué)到不少好東西,特別是閱讀代碼的能力,試問這么糟糕復(fù)雜的框架都可以精通,天下還有什么代碼能看不懂(別自信滿滿,你敢碰BOOST里面的那些奇技淫巧,匪夷所思的幻碼嗎)。但實在忍受不了軀體內(nèi)另一個人日日夜夜,旦夕不停的訴求,兼之又一直恨鐵不成鋼,又為自己失去的青春陣痛不已。而VC2008中BCG的出現(xiàn)之后,VC2010還依然保留著那些蠢笨無比的BCG文件,不思悔改,終于讓某徹底對MFC死心了,這貨沒得救了。
好吧,廢話少說,趕緊進(jìn)入正題。
國內(nèi)C++界中一大俠說過,任何東西,都要先抓住其主干,其他的一切細(xì)節(jié),都可以留待以后慢慢補(bǔ)充,否則,一開始就陷入細(xì)節(jié),想跳出來就不容易了。大俠此語,實在有道理。(不知為什么,此大俠后期一直在對C++表示不滿,當(dāng)然,人家現(xiàn)在的境界,豈是我等小民所能體會)。不說其他,就說在C++中,必須站在一個高度上,縱觀全局,才能將一切看得通通透透。于是,下面就嘗試爬上高峰,來俯瞰MFC中的眾生。MFC的主干,在下看來,既不是什么六大關(guān)鍵技術(shù),也不是什么文檔視圖結(jié)構(gòu),更不是COM的封裝,而是為了將原始的API中的各種對象,好比窗口、GDI、設(shè)備環(huán)境等,都映射成相應(yīng)的多線程安全的C++對象,好比將HWND搞成CWnd,以期減輕WINDOWS下界面開發(fā)的工作量。
這種想法,其實也無可厚非,如果真能封裝得好,確實可以提高生產(chǎn)代碼效率,但是從C++和WINDOWS的設(shè)計觀念中來看,這種映射,還是存在很大的問題,稍后再述。問題在于,MFC的實現(xiàn),異常丑陋呆板,通過幾個全局的線程安全局部變量的映射表,將句柄(Handle,此詞譯得相當(dāng)令人惡心)與其相應(yīng)的C++對象指針形成一一對應(yīng),并且指定,在大部分的消息處理和參數(shù)傳遞中,所有的參數(shù)由原本的句柄一律由對象指針來代替,然后就高高興興地宣稱已經(jīng)完成任務(wù)了,這實在讓人哭笑不得。不說別的,即使是消息處理,MFC即使已經(jīng)做了很大的努力,也都無法將句柄參數(shù)搞成對應(yīng)的對象指針,單是這一點(diǎn),就已經(jīng)失敗了。然后再看看,它最后封裝的做法,不過是將API函數(shù)中的第一個參數(shù)為句柄的都改成相應(yīng)的C++對象的函數(shù)成員,好比將SetWindowText搞成CWnd::SetWindowText,美其名曰,薄薄的封裝。這真是要笑掉大牙了,既然是這樣的薄薄的封裝,那還干脆不如不封裝了,我想破了頭,也看不出這樣的封裝,帶來了什么樣的好處。反而將原本全局函數(shù)的各種靈活性,都給葬送在類成員函數(shù)這個監(jiān)獄中。全局函數(shù)的各種好處,除了是全局這個缺陷之外,實在都是類成員函數(shù)沒法比的。而且,鑒于API中數(shù)量實在太多,于是搞出來的C++對象都是龐然大物,最臭名昭著的,當(dāng)然要數(shù)CWnd和CDC這對黑風(fēng)雙煞了,使用起來,極不方便,極難學(xué)習(xí)。一個類的函數(shù)過多,無論如何,都是失敗的設(shè)計。還有,如果WINDOWS系統(tǒng)新增了什么使用到句柄的函數(shù),這些類也都要相應(yīng)地增加新成員函數(shù),再進(jìn)一步說,如果用戶自己因為需要,也要開發(fā)使用到句柄的全局函數(shù),要怎么想辦法整成員函數(shù)呢。其實,MFC中的所謂的線程安全,其實也不過是自欺欺人的說法而已,你要想在MFC中多線程的使用窗口對象,即使不是不可能,那也要花費(fèi)九牛二力氣才能在多線程中共享窗口對象,一切皆只因MFC已經(jīng)很努力地做到了不讓你在多線程下使用窗口對象。
好了,再進(jìn)一步考察,在這種思路的指導(dǎo)下,最后MFC被設(shè)計成一個什么樣的框架。一直很懷疑想,MFC的設(shè)計者,是否不是對WINDOWS API的精神了解得不透徹,又或者對C++不是很精通,否則,無論如何,斷斷都不會整出MFC這樣的一個人不象人、鬼不似鬼,精神分裂、做事虎頭蛇尾的鬼東西出來。要不然,我們用MFC開發(fā),就不會覺得束手束腳,很不好施展拳腳,一再感覺一直受制于其可惡的呆板的框架之下,這并不是說框架不好,只是MFC這套框架,實在很不好擴(kuò)充。這樣也罷了,問題是,MFC在使用上,又很不人性化,你又要陷入各種各樣的細(xì)節(jié)之中,什么二段構(gòu)造,有些對象又要自己刪除,有些又不能自己刪除等,更要命的是,MFC的運(yùn)行效率又臭名昭著。有些同學(xué)就會問,貌似在MFC開發(fā),其實也很強(qiáng)大很靈活,殊不知這種強(qiáng)大和靈活,那都是源于C++的強(qiáng)大靈活,也源于WINDOWS系統(tǒng)的強(qiáng)大靈活,源于MFC只對API只作了一層薄薄的封裝,與MFC本身的設(shè)計沒什么太多的關(guān)聯(lián)。當(dāng)拋開MFC,直接用API和C++來寫代碼,就體會到什么才叫做強(qiáng)大靈活,除了代碼比MFC多一點(diǎn)之外,一切都要比MFC來得清爽,也要來得容易維護(hù)。如果,如果設(shè)計做得好,可能總體的代碼量不會比用MFC的多也說不定。
于是,這么一個偉大的框架,表現(xiàn)出來的,很多地方都C++的精神格格不入。承然,MFC剛開始設(shè)計時,缺乏太多的C++的特性的支持,而不得不另起爐灶。但是,就算沒有C++98中的功能支持,MFC也不該出現(xiàn)以下這么重大的設(shè)計問題。
1、 不需要用到的特性,我們依然要付出種種代價。我們看到CCmdTarget集成了COM的功能,CWnd中對ActiveX的支持,就算我們的代碼不包含一丁點(diǎn)使用到COM的代碼,我們都要背負(fù)每個CWnd中將近100個字節(jié)的大小,運(yùn)行過程中一再要忍受框架代碼中對ActiveX變量的一次又一次地檢查。我們看到,就算不使用文檔視圖結(jié)構(gòu),CMainFrame中都要承受對它的支持代碼,和各個不必要的成員變量。總之,MFC中有很多特性,就算不愿意使用,它們還是在那里,不容許我們忽視。
2、 耦合太厲害。我們看到,CWnd中居然出現(xiàn)了對子類CView和CFrameWnd的引用,CControlBar的成員函數(shù)中有對CToolBar的CAST,然后,CFrameWnd又引用到CControlBar和CView,CControlBar和CView也都用到CFrameWnd的函數(shù),各種各樣好厲害的循環(huán)依賴,這么糟糕的設(shè)計竟然出現(xiàn)在C++的著名的界面框架,不得不令人嘆息不已。這樣的耦合,就是在MFC之中,一個類設(shè)計得再好,也只能在某些特定的場合下使用。好比, CControlBar, CSplitterWnd這兩個好東西只能活在CFrameWnd,CView的環(huán)境下,想在其他的地方使用上就煞不容易了。就算是著名的文檔視圖結(jié)構(gòu),也都是問題很大,如果我的類,想要訂閱文檔中的通知消息,還要求必須派生于CView中,這個限制也太厲害了,于是為了它這個死板的設(shè)計,有多少次,我們必須改變設(shè)計,以適應(yīng)MFC的無理取鬧的要求。
3、 功能太過單薄:MFC僅僅是作為一個界面的基本框架而已,僅僅實現(xiàn)了從句柄到對象的映射,然后,再加上一點(diǎn)點(diǎn)所謂的文檔視圖的MVC模式,必要的多視圖多子窗口的支持,基本的必要的COM支持,對API的薄薄封裝,僅此而已(至于SOCKET、WININET和數(shù)據(jù)庫的MFC類,又有多少可利用的價值)。有多少次,我們想對對話框進(jìn)行拉伸的操作;有多少次,我們需要用到表格控件;……,但是,這一切,想要MFC提供更加強(qiáng)大的控件,完全都沒有。必須求助于第三方控件公司庫,好比,BCG、XTREME,你們知道,MFC的代碼就算再爛,也還是很有分寸,還能保住最基本的底線,但是,第三方控件的代碼,那真的是,讓人大驚失色,沒有做不到的,只有想不到的。要知道,C++可是很強(qiáng)大的,但是,用它做出來的框架,卻如此的功能之小。
4、 MFC中的類的功能不單一,承載了太多的責(zé)任,導(dǎo)致很容易就出現(xiàn)了很多重復(fù)的代碼:好比CCmdTarget中原本只是為了可以處理消息,后來居然還兼具COM的功能。又好比,CList, CMapPtrToPtr里面又都包含了內(nèi)存池的實現(xiàn)代碼,原本這些內(nèi)存池的實現(xiàn)代碼就應(yīng)該剝離出來。
5、 行為錯亂,神經(jīng)分裂,沒有統(tǒng)一的接口,以至于使用起來,極易出錯。很多地方,都努力地去做了,但是都做得不徹底。企圖封裝所有的WINDWOWS的界面的API,但無論如何,都做不到,而且它自己內(nèi)部也都知道沒辦法做到,但它還要努力地去做。
……
各種各樣的問題,難怪MFC的書那么多,沒有一本可以作為面向?qū)ο蟮牡浞秾W(xué)習(xí),即使四大天王,也不過只是沒有那么惡臭而已。原來一切,皆只因為源頭本就臟了。之前還以為,MFC,還能算是一積心堆砌而成垃圾。總體設(shè)計,雖然確實糟糕透頂,但細(xì)微上看,還能做到讓人擊節(jié)贊嘆,好比對于線程局部變量的封裝擴(kuò)展,消息映射表。可是,即使局部代碼的實現(xiàn),也都充滿了重重的缺陷。
如果說MFC還有些可圈可點(diǎn)之處,VC2008之后引進(jìn)的BCG的界面補(bǔ)丁,完全就將這些可稍為搬上臺面的東西直接給淹沒掉。BCG的出現(xiàn),對MFC而言,根本就不是狗尾續(xù)貂,而是狗尾之上再綁上一只蠢笨無比的大豬。直接就將MFC推入萬劫不復(fù)之地。
MFC,OH SHIT,你可以去死了!
突然驚覺,C++的類庫框架,無不充滿了各種各樣的缺陷,除了STL還差強(qiáng)人意之外(其實也問題多多),沒有一個能夠讓人看著滿意,用著放心。于是,我要投入LISP和C的懷抱之中了,不,一定要頂住,C++是我的最愛。