===============================================================================
⊙ 获取方法(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
和动态数组不太熟悉,就不作介绍了。