Symbian编程总结-深入篇-RTTI的实现及原理说明
本文章由杨芹勍原创,如需转载请注明作者及出处,否则保留追究法律责任的权利!
一、前言
RTTI(运行时类型信息)是被现代高级编程语言所普遍支持的特性之一,如C#中的“a is A”、JAVA中的“a instanceof A”都属于RTTI的范畴。然而Symbian OS C++并不支持这个特性,这导致由Win32、JAVA转向Symbian的开发人员或者代码的移植都带来很大的不便,本文将解决这个问题。
二、什么是RTTI
RTTI指的是“运行时类型识别(Run-Time Type Identification)”或者“运行时类型信息(Run-Time Type Information)”,程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型。
随着应用场合之不同﹐所需支持的RTTI范围也不同。最单纯的RTTI包括:
- 类识别(class identification)──包括类名称或ID。
- 继承关系(inheritance relationship)──支持执行时期的“往下变换类型”(downward casting),亦即动态变换类型(dynamic casting) 。
三、Symbian OS中的RTTI
由于Symbian系统以及它运行的硬件环境的限制,造成Symbian系统编程不能完全像一般C++程序设计随心所欲,Symbian OS C++并不提供对RTTI的支持。所以,标准C++中的dynamic_cast<>、typeid()及type_info都是不被支持的。
四、移植MFC代码实现RTTI
VC++编译器从4.0版才开始支持RTTI,但MFC 4.x 并未使用编译器的能力完成其对RTTI 的支持。MFC 有自己一套沿用已久的办法(从1.0 版就开始了)。在此,我们借用MFC中实现RTTI的代码,来完成对Symbian OS C++ RTTI的支持。
关于MFC中RTTI的实现原理,侯捷的《深入浅出MFC》里已经有详细的阐述,基本原理是使用几个特殊的宏手动的在编译期间确定一个对象继承关系链表,在此不再说明具体原理。
我们移植的是VC++ 9.0中MFC实现RTTI的代码,不使用侯捷在《深入浅出MFC》中所提供的模拟代码。因为侯捷的代码中存在非常多的“可写的静态数据”,将不能在Symbian DLL或者2nd版的APP中使用。然而,VC++ 9.0中的MFC代码没有存在以上问题,所以可以再任何Symbian代码中使用。
压缩包内包含两个文件:Rtti.h、Rtti.cpp。将这两个文件加入工程后,着手设计实现RTTI的类:
1、类的声明:
Rtti.h头文件中的CRttiBase是拥有RTTI特性的基础类,此类相当于MFC中的CObject,它继承自CBase,所有要实现RTTI特性的类都要从此类派生,并且在声明加入一个特殊的宏:
1 class CMyClass : public CRttiBase 2 { 3 DECLARE_DYNAMIC(CMyClass) 4 ... 5 };
注意:宏DECLARE_DYNAMIC中的第一个参数为当前类的类名:CMyClass。
声明第二个类继承自CMyClass,同样的,要加上DECLARE_DYNAMIC宏:
1 class CMyClass1 : public CMyClass 2 { 3 DECLARE_DYNAMIC(CMyClass1) 4 ... 5 };
注意:实现RTTI的子类继承自父类,而父类必须继承自CRttiBase。
2、类的实现
在CMyClass和CMyClass1的实现源文件分别加入以下两行代码:
1 IMPLEMENT_DYNAMIC(CMyClass, CRttiBase); 2 IMPLEMENT_DYNAMIC(CMyClass1, CMyClass);
宏IMPLEMENT_DYNAMIC中的第一个参数为当前子类型,第二个参数为直接父类型,如:CMyClass的直接父类为CRttiBase,CMyClass1的直接父类为CMyClass。
3、使用RTTI特性
通过以上简单两个步骤,我们就能使用RTTI特性了,完整代码:
1 class CMyClass : CRttiBase 2 { 3 DECLARE_DYNAMIC(CMyClass) 4 }; 5 6 class CMyClass1 : CMyClass 7 { 8 DECLARE_DYNAMIC(CMyClass1) 9 }; 10 11 class CMyClass2 : CRttiBase 12 { 13 DECLARE_DYNAMIC(CMyClass2) 14 }; 15 16 IMPLEMENT_DYNAMIC(CMyClass, CRttiBase); 17 IMPLEMENT_DYNAMIC(CMyClass1, CMyClass); 18 IMPLEMENT_DYNAMIC(CMyClass2, CRttiBase); 19 20 LOCAL_C void MainL() 21 { 22 CMyClass1* mc1 = new (ELeave) CMyClass1; 23 TBool a = mc1->IsKindOf(RUNTIME_CLASS(CMyClass)); 24 TBool b = mc1->IsKindOf(RUNTIME_CLASS(CRttiBase)); 25 TBool c = mc1->IsKindOf(RUNTIME_CLASS(CMyClass2)); 26 }
从代码中可以看出CMyClass1的父类为CMyClass,CMyClass的父类为RTTI基类CRttiBase,而CMyClass2的基类也为CRttiBase,CMyClass1和CMyClass2没有继承关系。
所以,代码第23至25行,abc的值依次为true、true、false。
CRttiBase::IsKindOf方法类似于C#中的“is”关键字、JAVA中的“instanceof”关键字,传入的是某个类的运行时信息,而宏“RUNTIME_CLASS”获取的是某个类的运行时信息“CRuntimeClass”。
4、运行时信息
“运行时信息”结构体CRuntimeClass在创建时将类的信息保存以便程序运行时查阅,其中包括类名、类大小、父类信息等。这些信息在宏IMPLEMENT_DYNAMIC内部,在程序编译的时候就已经确定:
1 struct CRuntimeClass 2 { 3 const char* iClassName; 4 TInt iObjectSize; 5 TUint iSchema; 6 CRttiBase* (*iCreateObjectProc)(); 7 CRuntimeClass* iBaseClass; 8 CRttiBase* CreateObject(); 9 TBool IsDerivedFrom(const CRuntimeClass* aBaseClass) const; 10 CRuntimeClass* iNextClass; 11 };
注:CRuntimeClass可以理解为C#中的System.Type类型。
5、获取类和对象的运行时信息
获取类的运行时信息使用宏RUNTIME_CLASS,如:
CRuntimeClass* classType = RUNTIME_CLASS(CMyClass);
注:以上代码可以理解为C#中的“Type classType = typeof(CTestClass);”方法取类的类型信息。
获取对象的运行时信息使用CRttiBase::GetRuntimeClass()方法,如:
CMyClass1* mc1 = new (ELeave) CMyClass1; CRuntimeClass* rc = mc1->GetRuntimeClass();
注:以上代码可以理解为C#中的“Type classType = theClass.GetType();”方法取对象的类型信息。
两种方法均返回CRuntimeClass*。
6、通过运行时信息动态创建对象
大家可能会注意到CRuntimeClass有一个方法叫“CreateObject”,此方法能够通过运行时信息动态的创建对象。这在某些实现比较复杂的功能往往是很有必要的。如:
有一个工厂,能够生产不同的零件,而能够生产的零件的类型是多种多样的。
在没有实现RTTI之前,我们可能会在工厂方法里写一个很大的case语句,针对不同的零件类型进行判断从而调用不同类的构造函数。
而实现了RTTI后,我们只需要保持一个零件类型和CRuntimeClass之间的哈希表,在工厂方法中向哈希表传入零件类型,找到CRuntimeClass后调用CRuntimeClass::CreateObject()方法即可。
要实现动态创建对象,必须把函数声明中的DECLARE_DYNAMIC改为DECLARE_DYNCREATE,把IMPLEMENT_DYNAMIC改为IMPLEMENT_DYNCREATE即可。如:
1 class CMyClass : CRttiBase 2 { 3 DECLARE_DYNCREATE(CMyClass) 4 }; 5 6 IMPLEMENT_DYNCREATE(CMyClass, CRttiBase);
这样,CMyClass的类型信息就能够提供动态创建对象的功能了。
五、注意事项
CRttiBase是实现了对RTTI特性支持的父类,系统本身没有提供对RTTI的支持。所以,要实现RTTI的类必须直接或间接的继承自CRttiBase,这通常会对我们的设计造成很大的影响。如:如果一个类为活动对象,继承自CActive,它又要实现RTTI特性,显然以下声明是错误的,因为CActive与CRttiBase都继承自CBase:
class CMyActiveObject: public CActive, public CRttiBase {...}
在此有两种方法解决:
- 采用Wrapper模式,封装CActive并导出接口
- 通过修改rtti.h,使CRttiBase不继承自CBase,每个基于RTTI的类都手动的指定基类CBase或其它,然后使用C++多重继承的支持实现类的设计。
六、参考文献
- 深入浅出MFC,侯捷
- 如何在运行时确定对象类型(RTTI)
- Symbian OS C++高效编程
本文章由杨芹勍原创,如需转载请注明作者及出处,否则保留追究法律责任的权利!