天天看点

Delphi RTTi 机制(3)

===============================================================================

⊙ 获取方法(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

;

(如果枚举元素没有按缺省的

基准定义,那么将不能产生 RTTI 信息,为什么?)

===============================================================================

⊙ 获取其它数据类型的 RTTI 信息

===============================================================================

上面讨论了几个典型的 RTTI 信息的运行,其它的数据类型的 RTTI 信息的获取方法与上面类似。由于这些操作更加简单,就不一一讨论。下面概述其它类型的 RTTI 信息的情况:

LongString、

WideString

和 Variant 没有 RTTI 信息;

ShortString

只有 MaxLength 信息;

浮点数类型只有 FloatType: TFloatType 信息;

TFloatType = (ftSingle, ftDouble, ftExtended, ftComp, ftCurr);

Int64

只有最大值和最小值信息(也是

64

位整数表示);

Interface

和动态数组不太熟悉,就不作介绍了。