首页 > 代码库 > MFC?VCL?

MFC?VCL?

 最近网上争论VC和DELPHI/BCB优劣的朋友甚多(其实不是最近,一直都很多),其实真正有分歧的多半在MFC和VCL两套类库的选择上。不知道诸位对这两套类库,或者说是Application Framework(下面简称AF)的理解究竟如何?
    对于一套AF,跟一套类库class library最大的不同,在于它是一套完整的已经构建好的框架,风格、结构都已经做好,你不得不去遵守,而类库讲究的是拿来主义,理想的境界应该是可装配的元件(Component),比如标准模板库STL就是一个最后的例证。AF大抵都有几大要素,侯sir的大作《深入浅出MFC》中详细讨论了MFC的六大关键技术,其中比较重要的是:运行期类型识别(RTTI)、动态生成(Dynamic Creation)、永久生存/序列化(Perisitance/Serialization)和消息映射(Message Mapping),还有一个候sir没讨论的线程安全(Thread Safety)。关于这些问题,我和myan大哥曾经在E-MAIL略有讨论,下面分别谈点我的想法。

    首先,现在流行的AF基本上都是采用的单继承,原因嘛,让我猜的话,从技术上讲,CObject/TObject/java.lang.Object可以通过RTTI或者相关信息,可以唯一确定父类,如果void*和多继承显然就不行,当然也许跟多继承的麻烦和菱形结构的尴尬有关系(详细的可以看看Thinking in C++里对多继承MI的论述);从商业角度想,谁都不愿意用户再去用别人的AF,趁此机会做个限制。当然单继承带来的就是一根非常非常huge的继承树,所以Java现在是越来越难学,正如同JDK是越来越臃肿庞大。

    RTTI是比较新的语言提供的能力,比如C++新标准,Object Pascal(DELPHI)和Java,主要是Dyanmic cast需要进行类型检查,以及抛出异常(exception,也有人喜欢叫“例外”)的时候需要输出一些跟类相关的诊断信息。其中C++的RTTI最为简单,只能获得类名和相关的继承信息;而Object Pascal和Java都更为复杂,甚至于可以或者属性名和方法名。作为C++形式的AF,标准C++提供的信息显然不够,于是MFC自成一家搞了一套自己的RTTI,然后成为各位Anti-MFC斗士口诛笔伐的对象。
顺便我帮微软辩护一下,其实仔细想想就应该明白Microsoft的无奈:C++标准只规定了typeid可以获得类名及相关信息,但对输出的格式、二进制的组织格式没有任何的规定,而typeid并不完全是编译器内部实现,还依赖于头文件<typeinfo>里定义的结构,于是天下大乱。比如一个class A,在Borland的编译器上获得的类名是“A” ,在Microsoft/Intel编译器上是“class A”,在gcc上是“1A”(如果是个模板类,gcc上甚至可能是乱码),且储存typeinfo的结构居然还不一样。而MFC是被Microsoft、Borland、Synamtec、Watcom等等诸多编译器使用并动态连接到MFCxx.dll,如果用标准C++的RTTI和exception,不当机才怪。所以您见过多少使用标准C++作为RTTI的AF?比较流行的比如Qt(KDE)和codeguru上可以看到的VCF(Visual Component Framework,C++写的),都是自成体系的RTTI。而Object Pascal(DELPHI)和Java都分别只由一家公司开发,当然不存在C++的问题。
    由于现在的AF基本上都是采用的单继承,所以可以获得父类的名称。MFC提供的RTTI其实跟标准C++差不多,主要也是获得类名和一个IsKindOf(标准C++里可以用dynamic_cast检验),而VCL和JDK的RTTI都可以获得所有的interface和所有的method的信息,这也是Sun抨击C++的论据之一。其实想来好笑,首先RTTI是所有特性中最简单,无非就是记录下一些信息而已,自己记不就完了?比如MFC就是用宏(macro,您不会喜欢叫它“巨集”吧?)的方式记录自己的RTTI,而codeguru上那个VCF更进一步,用宏记录下所有method和property的信息,这下就跟VCL/JDK一样了。而Qt(KDE)更干脆,直接用MOC帮你写。其次,RTTI过多导致臃肿,并且知道所有的method名字有用吗?无非是用于IDE了,对使用者可是没多少好处。

    动态生成和永久生存/序列化标准C++并没有提供,据说曾经要在STL搞一个,后来也没了下文。当然MFC也用宏是模拟的,不过serialize就要麻烦诸位自己动手写了。VCL是通过TStream类中的ReadComponent和WriteComponent实现,而且挺自动的,不用象MFC一样自己写。原因何在?VCL不是有属性(Property)吗?其实就是把所有的property的内容写了进去。如果把property看成一个smart pointer的话,其实smart pointer初始化的时候就可以注册,然后就能提供RTTI和自动的serialization了。从这里看,VCL的确要简单一些,当然C++写个smart pointer也可以做到。

    消息映射其实并不用说太多,宏的直接映射和VCL的委托模型其实原理上差得并不远。VCL的方式很直接,略有效率损失,MFC方式的详细讨论多看看侯sir的书就行了。我想多说一点的是Qt(KDE)所用的signal-slot模型,很有意思,直接connect就OK,不过很遗憾,这是建立在对C++的扩充(说难听点叫对C++标准的肆意践踏)上的,需要用它的MOC预处理一下。

    线程安全的问题是老大难了。C从UNIX脱胎到C++成型的时候,UNIX上还是以进程为概念,并没有线程的影子。只是到了GUI时代,后台处理跟前台响应(比如对鼠标动作的处理)争CPU的问题日益严重,现在几乎所有OS都有了线程的概念,可惜标准太多,比如POSIX和WINDOWS的线程模型就差得太远,导致写的程序跟平台相关性很大,C++标准里并没有对此作出什么回应。MFC里的线程模型就是WIN32的实现,主要是关键代码段(Critical Section)和一些内核对象(Event、Semaphore等等),VCL也主要实现了Critical Section和信号灯(Event)。在同步方面,由于MFC的半封装(MFC经常被骂的跟API关系太大)倒把这个责任扔给了API,问题不大;而VCL就惨了,其他线程根本不能干扰主线程的工作,只能申请,然后其实是以单线程的方式在工作,有时候真的不如不用。Java吹嘘的线程就不用多提了,借myan的e-mail里一句话,“Andrei和Dinkumware的Pete Becker说,Java的多线程是给不会编程的人用的,toy-like。”

    总结性说一下。
    MFC整个结构的实现主要是靠宏,一堆一堆令人头痛的、无法让编译器进行类型检查的宏。当然我相信微软的初衷是只让您用而不是让您去搞懂这些宏。VCL的实现主要是来自Borland对Object Pascal的扩充,比如__closure扩充了C++的类成员函数指针,__published增加的__property以及RTTI方面增加的__classid。看起来呢,MFC就显得拖沓繁复,但是兼容性很不错(要不怎么可以在这么多编译器上用呢?);而VCL的看起来挺简洁,不过这种肆意的扩充带来的恶果便是没有portability,所以VCL不会象MFC那样广泛的用在各种编译器上。
    在思想方面,MFC主要是Document/View的架构,限制性太强,这就不如VCL的Component随意组合来得漂亮。但是需要提一点,许多人认为VCL封装的层次高,比MFC更抽象,因此更好,最直接的例子就是VCL的TCanvas类和MFC的CDC类的对比。而我的看法是,虽然是AF,但是最后类与类之间的关系应该做到既能协同合作,耦合性、依赖性又要尽可能的小,这样才是真正的Component,可以随意组合的积木式Component。VCL里的Component组合只是针对VCL里的类而言,跟外界没什么联系,并且类之间的依赖关系太大,而MFC半封装的特性却使MFC在这方面做得很好。

    最后不得不说一句,我很讨厌AF,因为我讨厌那棵庞大的继承树,我讨厌那种抑制自由的框架结构。STL在这方面就做得非常好,而Microsoft和Borland也在前进,分别又有了另外一套类库,ATL和CLX。当然ATL还是秉承Microsoft不跨平台的一贯宗旨,但是写得实在是非常漂亮,彻底扔了MFC,充分运用了C++独有的模板和多继承技术,轻量。恕小弟实在才疏学浅,不敢乱加评价,诸位有兴趣多看看潘大侠的大作;而CLX从Borland提供的一些文档看来,结构跟VCL似乎没什么大的区别,还是老套的深继承树,当然这跟Borland非要把Object Pascal作为它的产品中轴有关系,而Object Pascal没有C++那些独特的能力。

    一家之言,希望大家发表自己的看法。

原文:http://www.newasp.net/tech/program/20108.html

MFC?VCL?