谨以此文祭奠过去多少年下来,日日夜夜学习MFC源代码的宝贵时光。
曾经以为MFC是世界上最神圣无比的东西,发誓在没彻底弄透MFC之前,绝不越雷池半步,碰都不要去碰LISP、ERLANG、PYTHON、VCL、ATL、STL、LOKI、BOOST各种美妙的好东西。即使一再听到高人说MFC设计得不好,非常糟糕,也都至死而不悔,然后只要稍微听到有人在说MFC的伟大,心里就特别高兴。买了各种各样的VC方面的书,什么四大天王、什么几十百千例、什么VC项目实例,彻夜苦读,单步调试,一次又一次地徘徊在MFC套来套去的各种美妙代码之中,观察里面各个对象的值。一次又一次地感叹MFC的伟大,一直在想,这辈子,如果能彻底地掌握MFC里面的一切秘密,朕就心满意足了。终于有一天,终于彻底醒悟,原来MFC真不是好东西,不过就是那些伎俩而已,真不该将它看得如此的伟大。这种念头一起来,再回过头来看MFC的一坨一坨代码,只觉得清晰无比,又感觉代码之中充满了各种无奈。接着再看看那些VC方面的书,除了四大天王还稍微有些可取之处,其他的任何一本,奇臭无比,没有一行代码经得起推敲, 文笔也糟糕无比,其内容又一再重复,抄来抄去,真正是一本又一本的狗屎。 感叹一下:这些书牺牲了多少宝贵的树木,又折磨死了多少读者的脑细胞,实在罪大恶极。
在MFC大河日下的当今的大环境之下,在下也不想对它落井添石,毕竟也在其上学到不少好东西,特别是阅读代码的能力,试问这么糟糕复杂的框架都可以精通,天下还有什么代码能看不懂(别自信满满,你敢碰BOOST里面的那些奇技淫巧,匪夷所思的幻码吗)。但实在忍受不了躯体内另一个人日日夜夜,旦夕不停的诉求,兼之又一直恨铁不成钢,又为自己失去的青春阵痛不已。而VC2008中BCG的出现之后,VC2010还依然保留着那些蠢笨无比的BCG文件,不思悔改,终于让某彻底对MFC死心了,这货没得救了。
好吧,废话少说,赶紧进入正题。
国内C++界中一大侠说过,任何东西,都要先抓住其主干,其他的一切细节,都可以留待以后慢慢补充,否则,一开始就陷入细节,想跳出来就不容易了。大侠此语,实在有道理。(不知为什么,此大侠后期一直在对C++表示不满,当然,人家现在的境界,岂是我等小民所能体会)。不说其他,就说在C++中,必须站在一个高度上,纵观全局,才能将一切看得通通透透。于是,下面就尝试爬上高峰,来俯瞰MFC中的众生。MFC的主干,在下看来,既不是什么六大关键技术,也不是什么文档视图结构,更不是COM的封装,而是为了将原始的API中的各种对象,好比窗口、GDI、设备环境等,都映射成相应的多线程安全的C++对象,好比将HWND搞成CWnd,以期减轻WINDOWS下界面开发的工作量。
这种想法,其实也无可厚非,如果真能封装得好,确实可以提高生产代码效率,但是从C++和WINDOWS的设计观念中来看,这种映射,还是存在很大的问题,稍后再述。问题在于,MFC的实现,异常丑陋呆板,通过几个全局的线程安全局部变量的映射表,将句柄(Handle,此词译得相当令人恶心)与其相应的C++对象指针形成一一对应,并且指定,在大部分的消息处理和参数传递中,所有的参数由原本的句柄一律由对象指针来代替,然后就高高兴兴地宣称已经完成任务了,这实在让人哭笑不得。不说别的,即使是消息处理,MFC即使已经做了很大的努力,也都无法将句柄参数搞成对应的对象指针,单是这一点,就已经失败了。然后再看看,它最后封装的做法,不过是将API函数中的第一个参数为句柄的都改成相应的C++对象的函数成员,好比将SetWindowText搞成CWnd::SetWindowText,美其名曰,薄薄的封装。这真是要笑掉大牙了,既然是这样的薄薄的封装,那还干脆不如不封装了,我想破了头,也看不出这样的封装,带来了什么样的好处。反而将原本全局函数的各种灵活性,都给葬送在类成员函数这个监狱中。全局函数的各种好处,除了是全局这个缺陷之外,实在都是类成员函数没法比的。而且,鉴于API中数量实在太多,于是搞出来的C++对象都是庞然大物,最臭名昭著的,当然要数CWnd和CDC这对黑风双煞了,使用起来,极不方便,极难学习。一个类的函数过多,无论如何,都是失败的设计。还有,如果WINDOWS系统新增了什么使用到句柄的函数,这些类也都要相应地增加新成员函数,再进一步说,如果用户自己因为需要,也要开发使用到句柄的全局函数,要怎么想办法整成员函数呢。其实,MFC中的所谓的线程安全,其实也不过是自欺欺人的说法而已,你要想在MFC中多线程的使用窗口对象,即使不是不可能,那也要花费九牛二力气才能在多线程中共享窗口对象,一切皆只因MFC已经很努力地做到了不让你在多线程下使用窗口对象。
好了,再进一步考察,在这种思路的指导下,最后MFC被设计成一个什么样的框架。一直很怀疑想,MFC的设计者,是否不是对WINDOWS API的精神了解得不透彻,又或者对C++不是很精通,否则,无论如何,断断都不会整出MFC这样的一个人不象人、鬼不似鬼,精神分裂、做事虎头蛇尾的鬼东西出来。要不然,我们用MFC开发,就不会觉得束手束脚,很不好施展拳脚,一再感觉一直受制于其可恶的呆板的框架之下,这并不是说框架不好,只是MFC这套框架,实在很不好扩充。这样也罢了,问题是,MFC在使用上,又很不人性化,你又要陷入各种各样的细节之中,什么二段构造,有些对象又要自己删除,有些又不能自己删除等,更要命的是,MFC的运行效率又臭名昭著。有些同学就会问,貌似在MFC开发,其实也很强大很灵活,殊不知这种强大和灵活,那都是源于C++的强大灵活,也源于WINDOWS系统的强大灵活,源于MFC只对API只作了一层薄薄的封装,与MFC本身的设计没什么太多的关联。当抛开MFC,直接用API和C++来写代码,就体会到什么才叫做强大灵活,除了代码比MFC多一点之外,一切都要比MFC来得清爽,也要来得容易维护。如果,如果设计做得好,可能总体的代码量不会比用MFC的多也说不定。
于是,这么一个伟大的框架,表现出来的,很多地方都C++的精神格格不入。承然,MFC刚开始设计时,缺乏太多的C++的特性的支持,而不得不另起炉灶。但是,就算没有C++98中的功能支持,MFC也不该出现以下这么重大的设计问题。
1、 不需要用到的特性,我们依然要付出种种代价。我们看到CCmdTarget集成了COM的功能,CWnd中对ActiveX的支持,就算我们的代码不包含一丁点使用到COM的代码,我们都要背负每个CWnd中将近100个字节的大小,运行过程中一再要忍受框架代码中对ActiveX变量的一次又一次地检查。我们看到,就算不使用文档视图结构,CMainFrame中都要承受对它的支持代码,和各个不必要的成员变量。总之,MFC中有很多特性,就算不愿意使用,它们还是在那里,不容许我们忽视。
2、 耦合太厉害。我们看到,CWnd中居然出现了对子类CView和CFrameWnd的引用,CControlBar的成员函数中有对CToolBar的CAST,然后,CFrameWnd又引用到CControlBar和CView,CControlBar和CView也都用到CFrameWnd的函数,各种各样好厉害的循环依赖,这么糟糕的设计竟然出现在C++的著名的界面框架,不得不令人叹息不已。这样的耦合,就是在MFC之中,一个类设计得再好,也只能在某些特定的场合下使用。好比, CControlBar, CSplitterWnd这两个好东西只能活在CFrameWnd,CView的环境下,想在其他的地方使用上就煞不容易了。就算是著名的文档视图结构,也都是问题很大,如果我的类,想要订阅文档中的通知消息,还要求必须派生于CView中,这个限制也太厉害了,于是为了它这个死板的设计,有多少次,我们必须改变设计,以适应MFC的无理取闹的要求。
3、 功能太过单薄:MFC仅仅是作为一个界面的基本框架而已,仅仅实现了从句柄到对象的映射,然后,再加上一点点所谓的文档视图的MVC模式,必要的多视图多子窗口的支持,基本的必要的COM支持,对API的薄薄封装,仅此而已(至于SOCKET、WININET和数据库的MFC类,又有多少可利用的价值)。有多少次,我们想对对话框进行拉伸的操作;有多少次,我们需要用到表格控件;……,但是,这一切,想要MFC提供更加强大的控件,完全都没有。必须求助于第三方控件公司库,好比,BCG、XTREME,你们知道,MFC的代码就算再烂,也还是很有分寸,还能保住最基本的底线,但是,第三方控件的代码,那真的是,让人大惊失色,没有做不到的,只有想不到的。要知道,C++可是很强大的,但是,用它做出来的框架,却如此的功能之小。
4、 MFC中的类的功能不单一,承载了太多的责任,导致很容易就出现了很多重复的代码:好比CCmdTarget中原本只是为了可以处理消息,后来居然还兼具COM的功能。又好比,CList, CMapPtrToPtr里面又都包含了内存池的实现代码,原本这些内存池的实现代码就应该剥离出来。
5、 行为错乱,神经分裂,没有统一的接口,以至于使用起来,极易出错。很多地方,都努力地去做了,但是都做得不彻底。企图封装所有的WINDWOWS的界面的API,但无论如何,都做不到,而且它自己内部也都知道没办法做到,但它还要努力地去做。
……
各种各样的问题,难怪MFC的书那么多,没有一本可以作为面向对象的典范学习,即使四大天王,也不过只是没有那么恶臭而已。原来一切,皆只因为源头本就脏了。之前还以为,MFC,还能算是一积心堆砌而成垃圾。总体设计,虽然确实糟糕透顶,但细微上看,还能做到让人击节赞叹,好比对于线程局部变量的封装扩展,消息映射表。可是,即使局部代码的实现,也都充满了重重的缺陷。
如果说MFC还有些可圈可点之处,VC2008之后引进的BCG的界面补丁,完全就将这些可稍为搬上台面的东西直接给淹没掉。BCG的出现,对MFC而言,根本就不是狗尾续貂,而是狗尾之上再绑上一只蠢笨无比的大猪。直接就将MFC推入万劫不复之地。
MFC,OH SHIT,你可以去死了!
突然惊觉,C++的类库框架,无不充满了各种各样的缺陷,除了STL还差强人意之外(其实也问题多多),没有一个能够让人看着满意,用着放心。于是,我要投入LISP和C的怀抱之中了,不,一定要顶住,C++是我的最爱。