Delphi 的RTTI机制浅探 联系客服

发布时间 : 星期六 文章Delphi 的RTTI机制浅探更新完毕开始阅读7f38b9c689eb172ded63b711

class 的 TTypeData 结构如下:

TTypeData = packed record case TTypeKind of tkClass: (

ClassType: TClass; // 类 (VMTptr)

ParentInfo: PPTypeInfo; // 父类的 RTTI 指针 PropCount: SmallInt; // 属性数量 UnitName: ShortStringBase; // 单元的名称

{PropData: TPropData}); // 属性的详细信息 end;

其中的 PropData 又是一个大小可变的字段。TPropData 的定义如下:

TPropData = packed record

PropCount: Word; // 属性数量

PropList: record end; // 占位符,真正的意义在下一行 {PropList: array[1..PropCount] of TPropInfo} end;

每个属性信息在内存中的结构就是 TPropInfo,它的定义如下:

PPropInfo = ^TPropInfo; TPropInfo = packed record

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] of PPropInfo;

我们可以使用 GetPropList 获得所有属性信息的指针数组,数组用完以后要记得用 FreeMem 把数组的内存清除。

{ TypInfo.pas }

function GetPropList(TypeInfo: PTypeInfo; out PropList: PPropList): Integer;

5

GetPropList 传入类的 TTypeInfo 指针和 TPropList 的指针,它为 PropList 分配一块内存后把该内存填充为指向 TPropInfo 的指针数组,最后返回属性的数量。 上面的例子演示了如何获得类的所有属性信息,也可以根据属性的名称单独获得属性信息:

{ TypInfo.pas }

function GetPropInfo(TypeInfo: PTypeInfo; const PropName: string): PPropInfo;

GetPropInfo 根据类的 RTTI 指针和属性的名称字符串,返回属性的信息 TPropInfo 的指针。如果没有找到该属性,则返回 nil。GetPropInfo 很容易使用,举个例子:

ShowMessage(GetPropInfo(TForm, 'Name')^.PropType^.Name);

这句调用显示了 TForm 类的 Name 属性的类型名称:TComponentName。

⊙ 获取方法(method)的类型信息

所谓方法就是以 of object 关键字声明的函数指针,下面的函数可以显示一个方法的类型信息:

procedure GetMethodTypeInfo(ATypeInfo: PTypeInfo; AStrings: TStrings); type

PParamData = ^TParamData;

TParamData = record // 函数参数的数据结构 Flags: TParamFlags; // 参数传递规则 ParamName: ShortString; // 参数的名称

TypeName: ShortString; // 参数的类型名称 end;

function GetParamFlagsName(AParamFlags: TParamFlags): string; var

I: Integer; begin

Result := '';

for I := Integer(pfVar) to Integer(pfOut) do begin if I = Integer(pfAddress) then Continue; if TParamFlag(I) in AParamFlags then

Result := Result + ' ' + GetEnumName(TypeInfo(TParamFlag), I); end; end; var

MethodTypeData: PTypeData; ParamData: PParamData; TypeStr: PShortString; I: Integer; begin

MethodTypeData := GetTypeData(ATypeInfo);

6

AStrings.Add('---------------------------------'); AStrings.Add('Method Name: ' + ATypeInfo^.Name);

AStrings.Add('Method Kind: ' + GetEnumName(TypeInfo(TMethodKind), Integer(MethodTypeData^.MethodKind)));

AStrings.Add('Params Count: '+ IntToStr(MethodTypeData^.ParamCount)); AStrings.Add('Params List:');

ParamData := PParamData(@MethodTypeData^.ParamList); for I := 1 to MethodTypeData^.ParamCount do begin

TypeStr := Pointer(Integer(@ParamData^.ParamName) + Length(ParamData^.ParamName) + 1);

AStrings.Add(Format(' [%s] %s: %s',[GetParamFlagsName(ParamData^.Flags), ParamData^.ParamName, TypeStr^]));

ParamData := PParamData(Integer(ParamData) + SizeOf(TParamFlags) + Length(ParamData^.ParamName) + Length(TypeStr^) + 2); end;

if MethodTypeData^.MethodKind = mkFunction then

AStrings.Add('Result Value: ' + PShortString(ParamData)^); end;

作为实验,在表单上放置一个 TListBox,然后执行以下代码,观察执行结果:

type

TMyMethod = function(A: array of Char; var B: TObject): Integer of object; procedure TForm1.FormCreate(Sender: TObject); begin

GetMethodTypeInfo(TypeInfo(TMyMethod), ListBox1.Items); GetMethodTypeInfo(TypeInfo(TMouseEvent), ListBox1.Items); GetMethodTypeInfo(TypeInfo(TKeyPressEvent), ListBox1.Items); GetMethodTypeInfo(TypeInfo(TMouseWheelEvent), ListBox1.Items); end;

由于获取方法的类型信息比较复杂,我尽量压缩代码也还是有这么长,让我们看看它的实现原理。GetMethodTypeInfo 的第一个参数是 PTypeInfo 类型,表示方法的类型信息地址。第二个参数是一个字符串列表,可以使用任何实现 TStrings 操作的对象。我们可以使用 System.pas 中的 TypeInfo 函数获得任何类型的 RTTI 信息指针。TypeInfo 函数像 SizeOf 一样,是内置于编译器中的。

GetMethodTypeInfo 还用到了 TypInfo.pas 中的 GetEnumName 函数。这个函数通过枚举类型的整数值得到枚举类型的名称。

function GetEnumName(TypeInfo: PTypeInfo; Value: Integer): string;

与获取类(class)的属性信息类似,方法的类型信息也在 TTypeData 结构中

TTypeData = packed record case TTypeKind of

7

tkMethod: (

MethodKind: TMethodKind; // 方法指针的类型 ParamCount: Byte; // 参数数量

ParamList: array[0..1023] of Char // 参数详细信息,见下行注释 {ParamList: array[1..ParamCount] of record

Flags: TParamFlags; // 参数传递规则 ParamName: ShortString; // 参数的名称 TypeName: ShortString; // 参数的类型 end;

ResultType: ShortString}); // 返回值的名称 end;

TMethodKind 是方法的类型,定义如下:

TMethodKind = (mkProcedure, mkFunction, mkConstructor, mkDestructor, mkClassProcedure, mkClassFunction, { Obsolete }

mkSafeProcedure, mkSafeFunction);

TParamsFlags 是参数传递的规则,定义如下:

TParamFlag = (pfVar, pfConst, pfArray, pfAddress, pfReference, pfOut); TParamFlags = set of TParamFlag;

由于 ParamName 和 TypeName 是变长字符串,不能直接取用该字段的值,而应该使用指针步进的方法,取出参数信息,所以上面的代码显得比较长。

⊙ 获取有序类型(ordinal)、集合(set)类型的 RTTI 信息

讨论完了属性和方法的 RTTI 信息之后再来看其它数据类型的 RTTI 就简单多了。所有获取 RTTI 的原理都是通过 GetTypeData 函数得到 TTypeData 的指针,再通过 TTypeInfo.TypeKind 来解析 TTypeData。任何数据类型的 TTypeInfo 指针可以通过 TypeInfo 函数获得。

有序类型的 TTypeData 定义如下:

TTypeData = packed record

tkInteger, tkChar, tkEnumeration, tkSet, tkWChar: ( OrdType: TOrdType; // 有序数值类型 case TTypeKind of case TTypeKind of

tkInteger, tkChar, tkEnumeration, tkWChar: ( MinValue: Longint; // 类型的最小值 MaxValue: Longint; // 类型的最大值 case TTypeKind of

8