天天看點

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

和動态數組不太熟悉,就不作介紹了。