第二部分
获取方法(method)的类型信息
获取有序类型(ordinal)、集合(set)类型的 RTTI 信息
⊙ 获取方法(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); 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 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 tkInteger, tkChar, tkWChar: (); tkEnumeration: ( BaseType: PPTypeInfo; // 指针的指针,它指向枚举的 PTypeInfo NameList: ShortStringBase; // 枚举的名称字符串(不能直接取用) EnumUnitName: ShortStringBase)); // 所在的单元名称(不能直接取用) tkSet: ( CompType: PPTypeInfo)); // 指向集合基类 RTTI 指针的指针 end;
下面是一个获取有序类型和集合类型的 RTTI 信息的函数:
procedure GetOrdTypeInfo(ATypeInfo: PTypeInfo; AStrings: TStrings); var OrdTypeData: PTypeData; I: Integer; begin OrdTypeData := GetTypeData(ATypeInfo); AStrings.Add('------------------------------------'); AStrings.Add('Type Name: ' + ATypeInfo^.Name); AStrings.Add('Type Kind: ' + GetEnumName(TypeInfo(TTypeKind), Integer(ATypeInfo^.Kind))); AStrings.Add('Data Type: ' + GetEnumName(TypeInfo(TOrdType), Integer(OrdTypeData^.OrdType))); if ATypeInfo^.Kind <> tkSet then begin AStrings.Add('Min Value: ' + IntToStr(OrdTypeData^.MinValue)); AStrings.Add('Max Value: ' + IntToStr(OrdTypeData^.MaxValue)); end; if ATypeInfo^.Kind = tkSet then GetOrdTypeInfo(OrdTypeData^.CompType^, AStrings); if ATypeInfo^.Kind = tkEnumeration then for I := OrdTypeData^.MinValue to OrdTypeData^.MaxValue do AStrings.Add(Format(' Value %d: %s', [I, GetEnumName(ATypeInfo, I)])); end;
在表单上放置一个 TListBox,运行以下代码查看结果:
type TMyEnum = (EnumA, EnumB, EnumC); procedure TForm1.FormCreate(Sender: TObject); begin GetOrdTypeInfo(TypeInfo(Char), ListBox1.Items); GetOrdTypeInfo(TypeInfo(Integer), ListBox1.Items); GetOrdTypeInfo(TypeInfo(TFormBorderStyle), ListBox1.Items); GetOrdTypeInfo(TypeInfo(TBorderIcons), ListBox1.Items); GetOrdTypeInfo(TypeInfo(TMyEnum), ListBox1.Items); end;
(如果枚举元素没有按缺省的 0 基准定义,那么将不能产生 RTTI 信息,为什么?)
获取其它数据类型的 RTTI 信息
上面讨论了几个典型的 RTTI 信息的运行,其它的数据类型的 RTTI 信息的获取方法与上面类似。由于这些操作更加简单,就不一一讨论。下面概述其它类型的 RTTI 信息的情况:
LongString、WideString 和 Variant 没有 RTTI 信息;
ShortString 只有 MaxLength 信息;
浮点数类型只有 FloatType: TFloatType 信息;
TFloatType = (ftSingle, ftDouble, ftExtended, ftComp, ftCurr);
Int64 只有最大值和最小值信息(也是 64 位整数表示);
Interface 和动态数组不太熟悉,就不作介绍了。