现在的位置: 首页 > 综合 > 正文

Delphi RTTi 机制(3)

2018年05月27日 ⁄ 综合 ⁄ 共 6164字 ⁄ 字号 评论关闭
===============================================================================
⊙ 获取方法(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
和动态数组不太熟悉,就不作介绍了。
  

抱歉!评论已关闭.