DELPHI RTTI 探索.docx
《DELPHI RTTI 探索.docx》由会员分享,可在线阅读,更多相关《DELPHI RTTI 探索.docx(18页珍藏版)》请在冰豆网上搜索。
![DELPHI RTTI 探索.docx](https://file1.bdocx.com/fileroot1/2023-5/20/e93796c8-ea6b-4612-9a52-2a2748bb91a1/e93796c8-ea6b-4612-9a52-2a2748bb91a11.gif)
DELPHIRTTI探索
问题:
Delphi的RTTI机制浅探(积分:
100,回复:
21,阅读:
352)
分类:
ObjectPascal(版主:
menxin,cAkk)
来自:
savetime,时间:
2004-1-210:
48:
00,ID:
2420610
[显示:
小字体|大字体]
Delphi的RTTI机制浅探
savetime2k@ 2004.1.20
目录
===============================================================================
⊙RTTI简介
⊙类(class)和VMT的关系
⊙类(class)、类的类(classofclass)、类变量(classvariable)的关系
⊙TObject.ClassType和TObject.ClassInfo
⊙is和as运算符的原理
⊙TTypeInfo–RTTI信息的结构
⊙获取类(class)的属性(property)信息
⊙获取方法(method)的类型信息
⊙获取有序类型(ordinal)、集合(set)类型的RTTI信息
⊙获取其它数据类型的RTTI信息
===============================================================================
本文排版格式为:
正文由窗口自动换行;所有代码以80字符为边界;中英文字符以空格符分隔。
(作者保留对本文的所有权利,未经作者同意请勿在在任何公共媒体转载。
)
正文
===============================================================================
⊙RTTI简介
===============================================================================
RTTI(Run-TimeTypeInformation)翻译过来的名称是“运行期类型信息”,也就是说可以在运行期获得数据类型或类(class)的信息。
这个RTTI到底有什么用处,我现在也说不清楚。
我是在阅读Delphi持续机制的代码中发现了很多RTTI的运用,只好先把RTTI学习一遍。
下面是我的学习笔记。
如果你发现了错误请告诉我。
谢谢!
Delphi的RTTI主要分为类(class)的RTTI和一般数据类型的RTTI,下面从类(class)开始。
===============================================================================
⊙类(class)和VMT的关系
===============================================================================
一个类(class),从编译器的角度来看就是一个指向VMT的指针(在后文用VMTptr表示)。
在类的VMTptr的负地址方向存储了一些类信息的指针,这些指针的值和指针所指的内容在编译后就确定了。
比如VMTptr-44的内容是指向类名称(ClassName)的指针。
不过一般不使用数值来访问这些类信息,而是通过System.pas中定义的以vmt开头的常量,如vtmClassName、vmtParent等来访问。
类的方法有两种:
对象级别的方法和类级别的方法。
两者的Self指针意义是不同的。
在对象级别的方法中Self指向对象地址空间,因此可以用它来访问对象的成员函数;在类级别的方法中Self指向类的VMT,因此只能用它来访问VMT信息,而不能访问对象的成员字段。
===============================================================================
⊙类(class)、类的类(classofclass)、类变量(classvariable)的关系
===============================================================================
上面说到类(class)就是VMTptr。
在Delphi中还可以用classof关键字定义类的类,并且可以使用类的类定义类变量。
从语法上理解这三者的关键并不难,把类当成普通的数据类型来考虑就可以了。
在编译器级别上表现如何呢?
为了简化讨论,我们使用TObject、TClass和TMyClass来代表上面说的三种类型:
type
TClass=classofTObject;
var
TMyClass:
TClass;
MyObject:
TObject;
begin
TMyClass:
=TObject;
MyObject:
=TObject.Create;
MyObject:
=TClass.Create;
MyObject:
=TMyClass.Create;
end;
在上面的例子中,三个TObject对象都被成功地创建了。
编译器的实现是:
TObject是一个VMTPtr常量。
TClass也是一个VMTptr常量,它的值就是TObject。
TMyClass是一个VMTptr变量,它被赋值为TObject。
TObject.Create与TClass.Create的汇编代码完全相同。
但TClass不仅缺省代表一个类,而且还(主要)代表了类的类型,可以用它来定义类变量,实现一些类级别的操作。
===============================================================================
⊙TObject.ClassType和TObject.ClassInfo
===============================================================================
functionTObject.ClassType:
TClass;
begin
Pointer(Result):
=PPointer(Self)^;
end;
TObject.ClassType是对象级别的方法,Self的值是指向对象内存空间的指针,对象内存空间的前4个字节是类的VMTptr。
因此这个函数的返回值就是类的VMTptr。
classfunctionTObject.ClassInfo:
Pointer;
begin
Result:
=PPointer(Integer(Self)+vmtTypeInfo)^;
end;
TObject.ClassInfo使用class关键字定义,因此是一个类级别的方法。
该方法中的Self指针就是VMTptr。
所以这个函数的返回值是VMTptr负方向的vmtTypeInfo的内容。
TObject.ClassInfo返回的Pointer指针,实际上是指向类的RTTI结构的指针。
但是不能访问TObject.ClassInfo指向的内容(TObject.ClassInfo返回值是0),因为Delphi只在TPersistent类及TPersistent的后继类中产生RTTI信息。
(从编译器的角度来看,这是在TPersistent类的声明之前使用{$M+}指示字的结果。
)
TObject还定义了一些获取类RTTI信息的函数,列举在下,就不一一分析了:
TObject.ClassName:
ShortString; 类的名称
TObject.ClassParent:
TClass; 对象的父类
TObject.InheritsFrom:
Boolean; 是否继承自某类
TObject.InstanceSize:
Longint; 对象实例的大小
===============================================================================
⊙is和as运算符的原理
===============================================================================
我们知道可以在运行期使用is关键字判断一个对象是否属于某个类,可以使用as关键字把某个对象安全地转换为某个类。
在编译器的层次上,is和as的操作是由System.pas中两个函数完成的。
{System.pas}
function_IsClass(Child:
TObject;Parent:
TClass):
Boolean;
begin
Result:
=(Child<>nil)andChild.InheritsFrom(Parent);
end;
_IsClass很简单,它使用TObject的InheritsForm函数判断该对象是否是从某个类或它的父类中继承下来的。
每个类的VMT中都有一项vmtParent指针,指向该类的父类的VMT。
TObject.InheritsFrom实际上是通过[递归]判断父类VMT指针是否等于自己的VMT指针来判断是否是从该类继承的。
{System.pas}
classfunctionTObject.InheritsFrom(AClass:
TClass):
Boolean;
var
ClassPtr:
TClass;
begin
ClassPtr:
=Self;
while(ClassPtr<>nil)and(ClassPtr<>AClass)do
ClassPtr:
=PPointer(Integer(ClassPtr)+vmtParent)^;
Result:
=ClassPtr=AClass;
end;
as操作符实际上是由System.pas中的_AsClass函数完成的。
它简单地调用is操作符判断对象是否属于某个类,如果不是就触发异常。
虽然_AsClass返回值为TObject类型,但编译器会自动把返回的对象改变为Parent类,否则返回的对象没有办法使用TObject之外的方法和数据。
{System.pas}
function_AsClass(Child:
TObject;Parent:
TClass):
TObject;
begin
Result:
=Child;
ifnot(ChildisParent)then
Error(reInvalidCast); //losesreturnaddress
end;
===============================================================================
⊙TTypeInfo–RTTI信息的结构
===============================================================================
RTTI信息的结构定义在TypInfo.pas中:
TTypeInfo=record //TTypeInfo是RTTI信息的结构
Kind:
TTypeKind; //RTTI信息的数据类型
Name:
ShortString; //数据类型的名称
{TypeData:
TTypeData} //RTTI的内容
end;
TTypeInfo就是RTTI信息的结构。
TObject.ClassInfo返回指向存放classTTypeInfo信息的指针。
Kind是枚举类型,它表示RTTI结构中所包含数据类型。
Name是数据类型的名称。
注意,最后一个字段TypeData被注释掉了,这说明该处的结构内容根据不同的数据类型有所不同。
TTypeKind枚举定义了可以使用RTTI信息的数据类型,它几乎包含了所有的Delphi数据类型,其中包括tkClass。
TTypeKind=(tkUnknown,tkInteger,tkChar,tkEnumeration,tkFloat,
tkString,tkSet,tkClass,tkMethod,tkWChar,tkLString,tkWString,
tkVariant,tkArray,tkRecord,tkInterface,tkInt64,tkDynArray);
TTypeData是个巨大的记录类型,在此不再列出,后文会根据需要列出该记录的内容。
===============================================================================
⊙获取类(class)的属性(property)信息
===============================================================================
这一段是RTTI中最复杂的部分,努力把本段吃透,后面的内容都是非常简单的。
下面是一个获取类的属性的例子:
procedureGetClassProperties(AClass:
TClass;AStrings:
TStrings);
var
PropCount,I:
SmallInt;
PropList:
PPropList;
PropStr:
string;
begin
PropCount:
=GetTypeData(AClass.ClassInfo).PropCount;
GetPropList(AClass.ClassInfo,PropList);
forI:
=0toPropCount-1do
begin
casePropList[I]^.PropType^.Kindof
tkClass :
PropStr:
='[Class]';
tkMethod :
PropStr:
='[Method]';
tkSet :
PropStr:
='[Set] ';
tkEnumeration:
PropStr:
='[Enum] ';
else
PropStr:
='[Field]';
end;
PropStr:
=PropStr+PropList[I]^.Name;
PropStr:
=PropStr+':
'+PropList[I]^.PropType^.Name;
AStrings.Add(PropStr);
end;
FreeMem(PropList);
end;
你可以在表单上放置一个TListBox,然后执行以下语句观察执行结果:
GetClassProperties(TForm1,ListBox1.Items);
该函数先使用GetTypeData函数获得类的属性数量。
GetTypeData是TypInfo.pas中的一个函数,它的功能是返回TTypeInfo的TypeData数据的指针:
{TypInfo.pas}
functionGetTypeData(TypeInfo:
PTypeInfo):
PTypeData;assembler;
class的TTypeData结构如下:
TTypeData=packedrecord
caseTTypeKindof
tkClass:
(
ClassType:
TClass; //类(VMTptr)
ParentInfo:
PPTypeInfo; //父类的RTTI指针
PropCount:
SmallInt; //属性数量
UnitName:
ShortStringBase;//单元的名称
{PropData:
TPropData}); //属性的详细信息
end;
其中的PropData又是一个大小可变的字段。
TPropData的定义如下:
TPropData=packedrecord
PropCount:
Word; //属性数量
PropList:
recordend; //占位符,真正的意义在下一行
{PropList:
array[1..PropCount]ofTPropInfo}
end;
每个属性信息在内存中的结构就是TPropInfo,它的定义如下:
PPropInfo=^TPropInfo;
TPropInfo=packedrecord
PropType:
PPTypeInfo; //属性类型信息指针的指针
GetProc:
Pointer; //属性的Get方法指针
SetProc:
Pointer; //属性的Set方法指针
StoredProc:
Pointer; //属性的StoredProc指针
Index:
Integer; //属性的Index值
Default:
Longint; //属性的Default值
NameIndex:
SmallInt; //属性的名称索引(以0开始计数)
Name:
ShortString; //属性的名称
end;
为了方便访问属性信息,TypInfo.pas中还定义了指向TPropInfo数组的指针:
PPropList=^TPropList;
TPropList=array[0..16379]ofPPropInfo;
我们可以使用GetPropList获得所有属性信息的指针数组,数组用完以后要记得用FreeMem把数组的内存清除。
{TypInfo.pas}
functionGetPropList(TypeInfo:
PTypeInfo;outPropList:
PPropList):
Integer;
GetPropList传入类的TTypeInfo指针和TPropList的指针,它为PropList分配一块内存后把该内存填充为指向TPropInfo的指针数组,最后返回属性的数量。
上面的例子演示了如何获得类的所有属性信息,也可以根据属性的名称单独获得属性信息:
{TypInfo.pas}
functionGetPropInfo(TypeInfo:
PTypeInfo;constPropName:
string):
PPropInfo;
GetPropInfo根据类的RTTI指针和属性的名称字符串,返回属性的信息TPropInfo的指针。
如果没有找到该属性,则返回nil。
GetPropInfo很容易使用,举个例子:
ShowMessage(GetPropInfo(TForm,'Name')^.PropType^.Name);
这句调用显示了TForm类的Name属性的类型名称:
TComponentName。
===============================================================================
⊙获取方法(method)的类型信息
===============================================================================
所谓方法就是以ofobject关键字声明的函数指针,下面的函数可以显示一个方法的类型信息:
procedureGetMethodTypeInfo(ATypeInfo:
PTypeInfo;AStrings:
TStrings);
type
PParamData=^TParamData;
TParamData=record //函数参数的数据结构
Flags:
TParamFlags; //参数传递规则
ParamName:
ShortString;//参数的名称
TypeName:
ShortString; //参数的类型名称
end;
functionGetParamFlagsName(AParamFlags:
TParamFlags):
string;
var
I:
Integer;
begin
Result:
='';
forI:
=Integer(pfVar)toInteger(pfOut)dobegin
ifI=Integer(pfAddress)thenContinue;
ifTParamFlag(I)inAParamFlagsthen
Result:
=Result+''+GetEnumName(TypeInfo(TParamFlag),I);
end;
end;
var
MethodTypeData:
PTypeData;
ParamData:
PParamData;
TypeStr:
PShortString;
I:
Integer;
begin
MethodTypeData:
=GetTypeData(ATypeInfo);
AStrings.Add('---------------------------------');
AStrings.Add('MethodName:
'+ATypeInfo^.Name);
AStrings.Add('MethodKind:
'+GetEnumName(TypeInfo(TM