VCF是一个C++编写的开源项目,全名Visual Component Framework,中文名:可视化组件框架。在这里先把我这两天的学习体会记录一下。
程序框架库应该可以算是系统级软件,它提供给用户一种相对固化的编程方式,进而简化系统编程。想想最开始在Win3.1下使用SDK开发程序的日子,我们真的应该感谢编出这些框架库的英雄们。
最早且最流行的框架库应该是Borland公司的OWL库(早期的Borland公司推出的每一样东西都是极品)。再后来也是目前最流行的框架库就是MFC了,C++Builder中的VCL库也是非常有特色的。最能评价MFC和VCL的好坏的人,应该是侯捷,他分别写了《深入浅出MFC》和《深入浅出VCL》,对每个库的分析都是非常不错的。
目前,随着计算机技术的发展,越来越多的人们对计算机的熟悉,框架库的数量和类别也越来越多,及外延也越来越广,以至于在某时,我们不知道某个库算不算框架库。就象前几天分析的WTL(它也变成开源项目了),以及目前名声在外的STL、BOOST、WXWINDOW等。他们也应该属于框架库吧。
还有三个软件是非常值得一提的:
1、AGG,一个类似于GDI+的2D图形库,写得非常的好,如果有空,我应该将前段时间分析AGG的经验写出来。
2、LOKI库,就是那个写《C++设计新思维》的作者提供的,如果有空,我应该将前段时间分析LOKI的经验写出来。
3、CPPUNIT库,一个单元测试的工具。
谈了这么多,还是重点讲一下VCF本身吧。
我看重并分析VCF的原因,应该是其RTTI功能,其次是它采用了比较先进的C++编程手法。
由于我正在编写一个开源的工控软件,想编写一种通用的属性处理程序,编了很长时间没有进展,后来仔细分析了一下我的需求,才发现我想要实现的功能,其实就是一个RTTI的扩展功能,何不直接找一个具有RTTI功能的库来分析一下呢?于是上网查找,发现VCF对RTTI的实现是最完整、最符合我的需要的。而其它库,对RTTI的实现,要么不完整,要么实现方法太差。
RTTI的实现,首先应该看语言本身对其的支持程度,象Delphi、C#、VB等语言本身就有Property属性的,对RTTI的实现相对就简单的多,而C++语言本身无支持Property的语法,而在CodeProject上看到的一些实现方法,都不是太好。
昨天花了一晚上将VCF的帮助文档看完,并分析了其RTTI的实现方法,体会如下:
1、由于VCF还处在开发阶段,其帮助文档很不完善,有许多内容都是空的;
2、VCF的源码则非常清晰,不愧不大家手笔;
3、VCF对RTTI的实现,包含在FoundationKit的RTTI目录中,包含了十多个文件,看其文件内容,其实现方法应该算是中规中矩的;
4、VCF实现了如下RTTI功能:
A、运行时查找对象所对应的类的名称、ID、描述;
B、类的继承关系;
C、Property功能;
D、Event功能;
E、Method功能;
F、为属性编辑而实现的Enum功能;
G、为属性编辑而实现的Item功能;
H、运行时基于名称和ID的动态对象创建功能;
I、Interface功能;
J、运行时类信息的注册功能;
等等。
5、从功能上讲,这非常接近我的需求,但还差如下功能:
A、基于属性的自动保存和读入功能(象Delphi那样);
B、运行时提供属性的隐藏和恢复功能,虽然目前通过其运行时增加和删除属性可以变通实现,但实现起来不方便;
C、属性的分层;
D、重点属性的标注;
6、另外,从其实现看,性能可能会是一个非常大的问题,因为其运行时对属性的访问都是通过名称来访问的,即便是通过UUID访问,也是通过UUID的字符串访问的,而实现上,在一个程序运行阶段,大部分类属性的访问是不需要通过名称来访问,而直接可以通过序号或编号来访问的。如果我想将它拿来用,一定要先对其性能进行重写。
今天分析完了VCF的RTTI相关代码,现将分析心得记录下来。
在VCF中,与RTTI相关的文件如下:
1、Property.h 属性的定义,包括事件属性的定义
2、Method.h 方法的定义,包括方法的参数定义
3、Field.h 字段的定义
4、Class.h、Class.Cpp 类RTTI的定义
5、InterfaceClass.h、InterfaceClass.Cpp 接口类和实现类的定义
6、ClassInfo.h 定义类定义时的宏操作
7、VCFRTTIImpl.h 类定义的具体实现类
8、ClassRegistry.h、ClassRegistry.Cpp 全局性的插入和记录整个系统中类RTTI信息
9、VariantData.h、VariantData.Cpp 定义VariantData数据结构,类似于VB的Variant变量
10、Enum.h 定义枚举型属性的枚举
一、Property的功能分析
Property提供一种对对象的读写操作方式,通过Property定义,用户可以采用如下方式访问对象:
Obj.GetValue("属性1");
Obj.SetValue("属性1",100);
在VCF中,Property被定义为抽象类,其定义中包括如下成员:
protected:
VariantData value_; // 属性的值
bool isCollection_; // 属性是否是集合类型,即属性是由其它值或属性组合而成
bool isReadOnly_; // 是否只读
private:
bool bound_; // 属性是否已绑定到对象,对于已绑定的属性,每次改写都会引发属性改变的事件
String name_; // 属性的名称
String displayName_; // 显示名称
String description_; // 描述
PropertyDescriptorType type_; // 类型
Object* source_; // 属性对应的实现对象
除了提供对这些成员的操作函数外,Property还定义了其它一些函数:
// 属性读写操作
virtual VariantData* get() = 0; // 从对象中读取属性,被定义为一个抽象方法,由子类重写
virtual void get(VariantData* value) = 0; // 住对象中写属性,被定义为一个抽象方法,由子类重写
virtual String toString(); 将属性的值转换为字符串
// 对集合型属性的访问操作
bool isCollection(); // 是否是集合型属性
virtual bool hasMoreElements( Object* source ); // 遍历集合中的值
virtual VariantData* nextElement( Object* source ) // 遍历集合中的值

virtual void startCollection( Object* source ) /**///// 遍历集合中的值
void add( VariantData* value ) // 改变集合集的内容
virtual void insert( Object* source, const unsigned long& index, VariantData* value ) // 改变集合集的内容
virtual void remove( Object* source, VariantData* value ) // 改变集合集的内容virtual bool collectionSupportsEditing() // 是否支持集合型属性编辑
可以看出:
1、Property只是一个接口类,其具体实现由继承子类完成,这是一种比较好的定义属性的方法,因为属性的操作、特别是属性的类型是很多的,如果在此实现Property的所有功能,将会严重限制Property的功能扩展;
2、从对集合型属性的处理来看,作者对设计模式的理解是比较深的,此处可以看作是Composite模式的一种特殊应用,对集合属性的操作则使用了Iterator模式,且定义了一种对序列型容器的通用遍历方法;
3、定义的value_是一个属性读写的缓冲区,即对同一类的所有对象,都是通过value_交换数据,value_被定义成一个VariantData,可以包含系统中使用的所有类型数据,但这存在一个问题,在多线程中,这是不安全的,不知VCF在程序的其它部分是否会对多线程模式下作特别处理;
二、EventProperty的功能分析
所谓Event,即事件,事件包括三个方面:事件的发生者、事件的处理者、事件本身的数据。举例说明:在控件中,针对鼠标移动,可以定义一个"OnMouseMove"事件处理过程。在此,控件是事件的发生者,OnMouseMove的处理对象和处理函数是事件的处理者,移动的数据和是否按下Ctrl键等,则是事件的本身。
在VCF中,EventProperty被定义为抽象类,其定义中包括如下成员:
protected:
String eventClassName_; // 事件发生者的类名称
String handlerClassName_; // 事件处理者的类名称
String delegateName_; // 事件处理函数的名称
Object* source_; // 对应的源对象
DelegateMethod delegateMethod_; // 事件处理方法
除了提供对这些成员的操作函数外,PropertyEvent还定义了其它一些函数:
virtual EventHandler* createEventHandler( Object* source, EventHandlerMethod method, const String& name ) = 0; // 新建一个事件的处理过程
由于未分析事件的实际例子,对其实际调用时机尚不得而知。
三、Method的功能分析
所谓Method,即方法,提供一种对象功能调用的机制,定义了方法,对对象的操作可以如下进行:
Obj.Method1(Para1,Para2);
以下是Method的成员定义:
protected:
String argTypes_; // 参数的类型定义,用一个字符串表示,字符串中的每一个字符代表一个参数的类型
ulong32 argCount_; // 参数的个数
bool hasReturnValue_; // 是否有返回值
String name_; // 方法的名称
其中参数的类型定义采用字符表示:
值 | 类型
----------------------------
"i" | integer
"l" | long
"h" | short
"+l" | unsigned long
"f" | float
"c" | char
"d" | double
"o" | Object*
"b" | bool
"s" | String or String&
"e" | Enum*
其成员函数只有如下一个值得关注:
// 执行方法
virtual VariantData* invoke( VariantData** arguments,Object* source=NULL ) = 0;
总的来说,方法的定义比较简单,处理起来也算一目了然,但其定义参数类型采用字符表示,个人认为不太方便,完全可以定义成枚举型。参数类型也不需要定义为一个字符数组,采用一个长度为6的字节数组就够了,因为其在定义参数接口时,只定义了最多6个参数的情况。
byte ArgTypes_[6];
四、InterfaceClass的功能分析
Iterface和ImplementedInterface分别定义了接口和接口实现,主要用来封装COM接口,比较简单,不再说明
五、Field的功能分析
比较简单,不再说明
六、Class的功能分析
待续......
七、类RTTI的声明过程
待续......
八、类RTTI的注册过程
待续......
九、VariantData的功能分析
待续......
十、总体分析
1、关于VariantData
为了提供对不同数据的访问接口,VCF中定义了VariantData数据类型,其定义类似于VB中的Variant类型,但类型没有VB中那么丰富,且其对String是单独处理,个人觉得,这种对String的单独处理是一种不得已而为之的处理,因为在VCF中,String被定义为一种std::basic_string<w_char>的简单封装,没有自动回收机制,因此,不能与其它简单数据(如int)同样处理。但应该会有一种和谐的处理方法的,上次在网上看到一个名为CSDTString的类,它是对STL中字符串类的仿MFC的CString的封装,过几天要仔细看看,看是否能用在此处。
2、关于RTTI的声明过程
RTTI的声明过程,类似于MFC中对消息处理的封装,也是采用了一系列的宏,这样定义后,在类中声明属性和方法的格式如下所示:
BEGIN_CLASSINFO(Foo, "Foo", "VCF::Object", FOO_CLASSID)
OBJECT_PROPERTY( Foo, "fooObj", Foo::getFoo, Foo::setFoo )
END_CLASSINFO(Foo)
这样的处理有优点也有缺点,其缺点不是说其编写方式(采用其它方法,也许其属性的编写方式会更长更难看),而是指其宏展开后的内容。其展开后,是一个嵌入在Object中的一个嵌入类,这个嵌入类在创建时,判断类的信息是否已在系统中注册,如果未注册,则将类、属性、方法等向系统注册。我目前尚未找到一种更好的处理方法,但能明显看出其在处理时有如下缺点:
A、类在每一次实例化时,都会产生一个嵌入类,虽然这个嵌入类是轻量级的;
B、嵌入类在判断是否注册,是向系统查询,是否存在这个类的注册信息,但作为类来说,完成可以有更简单的方法来判断其是否已向系统注册过,只需要一个静态标志即可;
另外,我好象在《C++编程新思维》一书中看到,所有具有这种处理形式的类,完全可以通过模板来实现,具体方法还要查查书。
3、关于属性的改动事件
VCF中对属性的改动事件,是通过一个嵌入的委托类实现,思想很好,但似乎有设计漏洞:
A、只要属性被绑定到对象,则每一次set函数的调用,都会激发属性改变的事件,但如果set调用而并没有改变属性,这种事件是多余的;
B、每次改变事件发生时,它都会将改变前的值和改变后的值通过事件封装包发送到观察者,但很多情况下,改变后的值并不是送进来的值,比如:设置某控件的长度为123,但控制在得到123时,可能会对经修改,以处理对齐等功能,这样,改变的值可能是125而不是123;
C、属性的改变发生时,很可能会产生多个属性的联动,这种联动,如果采用每个属性的改变都会触发事件,则效率太低,如果不处理,则可能导致View和Document的不一致;
D、作为工控软件,应该要考虑到效率,所以,对属性的改变应该要增加批处理的功能;
4、关于String
VCF中,将String定义为Unicode字符值,因此,可以对中文进行处理,但在系统中,大量的函数都如下所示:
String GetName() { return name_;}
注意到它返回的是一个对象而不是引用,对性能肯定会有非常大的影响。就其原因,应该是系统的设计者想将String类型的数据作为一个简单类型的值,与其它简单类型的值同样处理。统一是一回事,性能又是一回事,一个用户是否选用某个系统,他很可能就因为其运行速度不行,或是程序的尺寸太大,而否决这个系统(想起了我在最后一家公司,那个伟大的7.0系统)。
5、关系性能
我分析VCF的过程中,我对其性能有非常大的疑问,因为其对RTTI的访问,都是通过字符串的查找进行的。
在一个典型的工控应用程序中,对RTTI的操作应该包括如下几个方面:
A、在组态环境下,通过属性来改变控件的值,如改变字体、颜色等。
这种应用情况下,系统是能准确知道那些类需要改变的(主要是选择列表),因此不需要通过对象的名称来访问对象。因此,在对象中,增加一个指向其RTTI信息的指针是非常必要的;
B、组态环境下,通过RTTI,从文件中读取数据,直接生成控件列表。
这种应用情况下,只能通过名字来查询,但应该可以通过一些特殊处理,对同一类型的对象只查一次表;
C、组态环境下,调用对象的属性、方法、事件等
这种应用情况下,对属性、方法、事件应该可能通过名称和序号两种方式访问,因此,增加属性、方法、事件的序号访问方法是必要的;
D、运行环境下,调用对象的属性、方法、事件等
这种应用情况的处理,决定了系统在运行时的性能,由于对象的属性、方法、事件等都是用户脚本程序自己编写的,用户在写脚本时,肯定是通过名称来处理的,如:
窗口1.控件2.宽=100
但由于我们同时也是脚本语言的设计者,完全可以通过预处理,将这种通过名称查询的语句解释成通过序号查询的功能调用,伪码如下:
系统.窗口[1].控件[2].SetWidth(100)
当然,由于用户可能在运行时增加脚本,因此,通过字符操作的属性处理也需要保留;
6、关于属性编辑
VCF的属性定义中,已将属性编辑的绝大部分内容定义了,但对属性的编辑,还可以参考Delphi的实现,提供自定义属性编辑器。
十一、总结
1、VCF是我看到的对RTTI处理最完整的开源项目;
2、其中对RTTI处理,有许多可取之处,特别是其对属性的封装,以及模板化的子类实现,都是非常好的思想;
3、也有许多值得推敲的地方,如果要将它应用到我的项目中,满足我的要求,需要改进重写;
4、VCF中除了RTTI外,还有其它部分的功能吸引我,准备在接下来的几天内,将那些内容也好好分析一下。
补充一点,我昨天提的AGG,在VCF中是作为第三方产品,在底层实现2D图形的处理,看来,AGG还真是好东西呀。