⊙RTTI 简介
⊙ 类(class) 和 VMT 的关系
⊙ 类(class)、类的类(class of class)、类变量(class variable) 的关系
⊙TObject.ClassType 和 TObject.ClassInfo
⊙ is 和 as 运算符的原理
⊙TTypeInfo – RTTI 信息的结构
⊙ 获取类(class)的属性(property)信息
⊙ 获取方法(method)的类型信息
⊙ 获取有序类型(ordinal)、集合(set)类型的 RTTI 信息
⊙ 获取其它数据类型的 RTTI 信息
===============================================================================
本文排版格式为:
(作者保留对本文的所有权利,未经作者同意请勿在在任何公共媒体转载。)
正文
===============================================================================
⊙RTTI 简介
===============================================================================
RTTI(Run-Time Type Information) 翻译过来的名称是“运行期类型信息”,也就是说可以在运行期获得数据类型或类(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)、类的类(class of class)、类变量(class variable) 的关系
===============================================================================
上面说到类(class) 就是 VMTptr。在 Delphi 中还可以用 class of 关键字定义类的类,并且可以使用类的类定义类变量。从语法上理解这三者的关键并不难,把类当成普通的数据类型来考虑就可以了。在编译器级别上表现如何呢?
为了简化讨论,我们使用 TObject、TClass 和 TMyClass 来代表上面说的三种类型:
type
var
begin
end;
在上面的例子中,三个 TObject 对象都被成功地创建了。编译器的实现是:TObject 是一个 VMTPtr 常量。TClass 也是一个 VMTptr 常量,它的值就是 TObject。TMyClass 是一个 VMTptr 变量,它被赋值为 TObject。TObject.Create 与 TClass.Create 的汇编代码完全相同。但 TClass 不仅缺省代表一个类,而且还(主要)代表了类的类型,可以用它来定义类变量,实现一些类级别的操作。
===============================================================================
⊙TObject.ClassType 和 TObject.ClassInfo
===============================================================================
function TObject.ClassType: TClass;
begin
end;
TObject.ClassType 是对象级别的方法,Self 的值是指向对象内存空间的指针,对象内存空间的前 4 个字节是类的 VMTptr。因此这个函数的返回值就是类的 VMTptr。
class function TObject.ClassInfo: Pointer;
begin
end;
TObject.ClassInfo
TObject.ClassInfo 返回的 Pointer 指针,实际上是指向类的 RTTI 结构的指针。但是不能访问 TObject.ClassInfo 指向的内容(TObject.ClassInfo 返回值是 0),因为 Delphi 只在 TPersistent 类及 TPersistent 的后继类中产生 RTTI 信息。(从编译器的角度来看,这是在 TPersistent 类的声明之前使用 {$M+} 指示字的结果。)
TObject 还定义了一些获取类 RTTI 信息的函数,列举在下,就不一一分析了:
===============================================================================
⊙ is 和 as 运算符的原理
===============================================================================
我们知道可以在运行期使用 is 关键字判断一个对象是否属于某个类,可以使用 as 关键字把某个对象安全地转换为某个类。在编译器的层次上,is 和 as 的操作是由 System.pas 中两个函数完成的。
{ System.pas }
function _IsClass(Child: TObject; Parent: TClass): Boolean;
begin
end;
_IsClass
{ System.pas }
class function TObject.InheritsFrom(AClass: TClass): Boolean;
var
begin
end;
as
{ System.pas }
function _AsClass(Child: TObject; Parent: TClass): TObject;
begin
end;
===============================================================================
===============================================================================
RTTI 信息的结构定义在 TypInfo.pas 中:
TTypeInfo
TTypeKind 枚举定义了可以使用 RTTI 信息的数据类型,它几乎包含了所有的 Delphi 数据类型,其中包括 tkClass。
TTypeData
===============================================================================
⊙ 获取类(class)的属性(property)信息
===============================================================================
这一段是 RTTI 中最复杂的部分,努力把本段吃透,后面的内容都是非常简单的。
下面是一个获取类的属性的例子:
procedure GetClassProperties(AClass: TClass; AStrings: TStrings);
var
begin
end;
{ TypInfo.pas }
function GetTypeData(TypeInfo: PTypeInfo): PTypeData; assembler;
class 的 TTypeData 结构如下:
{ TypInfo.pas }
function GetPropList(TypeInfo: PTypeInfo; out PropList: PPropList): Integer;
GetPropList 传入类的 TTypeInfo 指针和 TPropList 的指针,它为 PropList 分配一块内存后把该内存填充为指向 TPropInfo 的指针数组,最后返回属性的数量。
上面的例子演示了如何获得类的所有属性信息,也可以根据属性的名称单独获得属性信息:
{ TypInfo.pas }
function GetPropInfo(TypeInfo: PTypeInfo; const PropName: string): PPropInfo;
GetPropInfo 根据类的 RTTI 指针和属性的名称字符串,返回属性的信息 TPropInfo 的指针。如果没有找到该属性,则返回 nil。GetPropInfo 很容易使用,举个例子:
===============================================================================
⊙ 获取方法(method)的类型信息
===============================================================================
所谓方法就是以 of object 关键字声明的函数指针,下面的函数可以显示一个方法的类型信息:
procedure GetMethodTypeInfo(ATypeInfo: PTypeInfo; AStrings: TStrings);
type
var
begin
end;