|
目錄 =============================================================================== ⊙ RTTI 簡(jiǎn)介 ⊙ 類(lèi)(class) 和 VMT 的關(guān)系 ⊙ 類(lèi)(class)、類(lèi)的類(lèi)(class of class)、類(lèi)變量(class variable) 的關(guān)系 ⊙ TObject.ClassType 和 TObject.ClassInfo ⊙ is 和 as 運(yùn)算符的原理 ⊙ TTypeInfo – RTTI 信息的結(jié)構(gòu) ⊙ 獲取類(lèi)(class)的屬性(property)信息 ⊙ 獲取方法(method)的類(lèi)型信息 ⊙ 獲取有序類(lèi)型(ordinal)、集合(set)類(lèi)型的 RTTI 信息 ⊙ 獲取其它數(shù)據(jù)類(lèi)型的 RTTI 信息 ===============================================================================
本文排版格式為: 正文由窗口自動(dòng)換行;所有代碼以 80 字符為邊界;中英文字符以空格符分隔。
(作者保留對(duì)本文的所有權(quán)利,未經(jīng)作者同意請(qǐng)勿在在任何公共媒體轉(zhuǎn)載。)
正文 =============================================================================== ⊙ RTTI 簡(jiǎn)介 ===============================================================================
RTTI(Run-Time Type Information) 翻譯過(guò)來(lái)的名稱(chēng)是“運(yùn)行期類(lèi)型信息”,也就是說(shuō)可以在運(yùn)行期獲得數(shù)據(jù)類(lèi)型或類(lèi)(class)的信息。這個(gè) RTTI 到底有什么用處,我現(xiàn)在也說(shuō)不清楚。我是在閱讀 Delphi 持續(xù)機(jī)制的代碼中發(fā)現(xiàn)了很多 RTTI 的運(yùn)用,只好先把 RTTI 學(xué)習(xí)一遍。下面是我的學(xué)習(xí)筆記。如果你發(fā)現(xiàn)了錯(cuò)誤請(qǐng)告訴我。謝謝!
Delphi 的 RTTI 主要分為類(lèi)(class)的 RTTI 和一般數(shù)據(jù)類(lèi)型的 RTTI,下面從類(lèi)(class)開(kāi)始。
=============================================================================== ⊙ 類(lèi)(class) 和 VMT 的關(guān)系 ===============================================================================
一個(gè)類(lèi)(class),從編譯器的角度來(lái)看就是一個(gè)指向 VMT 的指針(在后文用 VMTptr 表示)。在類(lèi)的 VMTptr 的負(fù)地址方向存儲(chǔ)了一些類(lèi)信息的指針,這些指針的值和指針?biāo)傅膬?nèi)容在編譯后就確定了。比如 VMTptr - 44 的內(nèi)容是指向類(lèi)名稱(chēng)(ClassName)的指針。不過(guò)一般不使用數(shù)值來(lái)訪(fǎng)問(wèn)這些類(lèi)信息,而是通過(guò) System.pas 中定義的以 vmt 開(kāi)頭的常量,如 vtmClassName、vmtParent 等來(lái)訪(fǎng)問(wèn)。
類(lèi)的方法有兩種:對(duì)象級(jí)別的方法和類(lèi)級(jí)別的方法。兩者的 Self 指針意義是不同的。在對(duì)象級(jí)別的方法中 Self 指向?qū)ο蟮刂房臻g,因此可以用它來(lái)訪(fǎng)問(wèn)對(duì)象的成員函數(shù);在類(lèi)級(jí)別的方法中 Self 指向類(lèi)的 VMT,因此只能用它來(lái)訪(fǎng)問(wèn) VMT 信息,而不能訪(fǎng)問(wèn)對(duì)象的成員字段。
=============================================================================== ⊙ 類(lèi)(class)、類(lèi)的類(lèi)(class of class)、類(lèi)變量(class variable) 的關(guān)系 ===============================================================================
上面說(shuō)到類(lèi)(class) 就是 VMTptr。在 Delphi 中還可以用 class of 關(guān)鍵字定義類(lèi)的類(lèi),并且可以使用類(lèi)的類(lèi)定義類(lèi)變量。從語(yǔ)法上理解這三者的關(guān)鍵并不難,把類(lèi)當(dāng)成普通的數(shù)據(jù)類(lèi)型來(lái)考慮就可以了。在編譯器級(jí)別上表現(xiàn)如何呢?
為了簡(jiǎn)化討論,我們使用 TObject、TClass 和 TMyClass 來(lái)代表上面說(shuō)的三種類(lèi)型:
type TClass = class of TObject; var TMyClass: TClass; MyObject: TObject; begin TMyClass := TObject; MyObject := TObject.Create; MyObject := TClass.Create; MyObject := TMyClass.Create; end; 在上面的例子中,三個(gè) TObject 對(duì)象都被成功地創(chuàng)建了。編譯器的實(shí)現(xiàn)是:TObject 是一個(gè) VMTPtr 常量。TClass 也是一個(gè) VMTptr 常量,它的值就是 TObject。TMyClass 是一個(gè) VMTptr 變量,它被賦值為 TObject。TObject.Create 與 TClass.Create 的匯編代碼完全相同。但 TClass 不僅缺省代表一個(gè)類(lèi),而且還(主要)代表了類(lèi)的類(lèi)型,可以用它來(lái)定義類(lèi)變量,實(shí)現(xiàn)一些類(lèi)級(jí)別的操作。
=============================================================================== ⊙ TObject.ClassType 和 TObject.ClassInfo ===============================================================================
function TObject.ClassType: TClass; begin Pointer(Result) := PPointer(Self)^; end;
TObject.ClassType 是對(duì)象級(jí)別的方法,Self 的值是指向?qū)ο髢?nèi)存空間的指針,對(duì)象內(nèi)存空間的前 4 個(gè)字節(jié)是類(lèi)的 VMTptr。因此這個(gè)函數(shù)的返回值就是類(lèi)的 VMTptr。
class function TObject.ClassInfo: Pointer; begin Result := PPointer(Integer(Self) + vmtTypeInfo)^; end;
TObject.ClassInfo 使用 class 關(guān)鍵字定義,因此是一個(gè)類(lèi)級(jí)別的方法。該方法中的 Self 指針就是 VMTptr。所以這個(gè)函數(shù)的返回值是 VMTptr 負(fù)方向的 vmtTypeInfo 的內(nèi)容。
TObject.ClassInfo 返回的 Pointer 指針,實(shí)際上是指向類(lèi)的 RTTI 結(jié)構(gòu)的指針。但是不能訪(fǎng)問(wèn) TObject.ClassInfo 指向的內(nèi)容(TObject.ClassInfo 返回值是 0),因?yàn)?Delphi 只在 TPersistent 類(lèi)及 TPersistent 的后繼類(lèi)中產(chǎn)生 RTTI 信息。(從編譯器的角度來(lái)看,這是在 TPersistent 類(lèi)的聲明之前使用 {$M+} 指示字的結(jié)果。)
TObject 還定義了一些獲取類(lèi) RTTI 信息的函數(shù),列舉在下,就不一一分析了:
TObject.ClassName: ShortString; 類(lèi)的名稱(chēng) TObject.ClassParent: TClass; 對(duì)象的父類(lèi) TObject.InheritsFrom: Boolean; 是否繼承自某類(lèi) TObject.InstanceSize: Longint; 對(duì)象實(shí)例的大小
=============================================================================== ⊙ is 和 as 運(yùn)算符的原理 ===============================================================================
我們知道可以在運(yùn)行期使用 is 關(guān)鍵字判斷一個(gè)對(duì)象是否屬于某個(gè)類(lèi),可以使用 as 關(guān)鍵字把某個(gè)對(duì)象安全地轉(zhuǎn)換為某個(gè)類(lèi)。在編譯器的層次上,is 和 as 的操作是由 System.pas 中兩個(gè)函數(shù)完成的。
{ System.pas } function _IsClass(Child: TObject; Parent: TClass): Boolean; begin Result := (Child <> nil) and Child.InheritsFrom(Parent); end;
_IsClass 很簡(jiǎn)單,它使用 TObject 的 InheritsForm 函數(shù)判斷該對(duì)象是否是從某個(gè)類(lèi)或它的父類(lèi)中繼承下來(lái)的。每個(gè)類(lèi)的 VMT 中都有一項(xiàng) vmtParent 指針,指向該類(lèi)的父類(lèi)的 VMT。TObject.InheritsFrom 實(shí)際上是通過(guò)[遞歸]判斷父類(lèi) VMT 指針是否等于自己的 VMT 指針來(lái)判斷是否是從該類(lèi)繼承的。
{ System.pas } class function TObject.InheritsFrom(AClass: TClass): Boolean; var ClassPtr: TClass; begin ClassPtr := Self; while (ClassPtr <> nil) and (ClassPtr <> AClass) do ClassPtr := PPointer(Integer(ClassPtr) + vmtParent)^; Result := ClassPtr = AClass; end;
as 操作符實(shí)際上是由 System.pas 中的 _AsClass 函數(shù)完成的。它簡(jiǎn)單地調(diào)用 is 操作符判斷對(duì)象是否屬于某個(gè)類(lèi),如果不是就觸發(fā)異常。雖然 _AsClass 返回值為 TObject 類(lèi)型,但編譯器會(huì)自動(dòng)把返回的對(duì)象改變?yōu)?Parent 類(lèi),否則返回的對(duì)象沒(méi)有辦法使用 TObject 之外的方法和數(shù)據(jù)。
{ System.pas } function _AsClass(Child: TObject; Parent: TClass): TObject; begin Result := Child; if not (Child is Parent) then Error(reInvalidCast); // loses return address end;
=============================================================================== ⊙ TTypeInfo – RTTI 信息的結(jié)構(gòu) ===============================================================================
RTTI 信息的結(jié)構(gòu)定義在 TypInfo.pas 中:
TTypeInfo = record // TTypeInfo 是 RTTI 信息的結(jié)構(gòu) Kind: TTypeKind; // RTTI 信息的數(shù)據(jù)類(lèi)型 Name: ShortString; // 數(shù)據(jù)類(lèi)型的名稱(chēng) {TypeData: TTypeData} // RTTI 的內(nèi)容 end;
TTypeInfo 就是 RTTI 信息的結(jié)構(gòu)。TObject.ClassInfo 返回指向存放 class TTypeInfo 信息的指針。Kind 是枚舉類(lèi)型,它表示 RTTI 結(jié)構(gòu)中所包含數(shù)據(jù)類(lèi)型。Name 是數(shù)據(jù)類(lèi)型的名稱(chēng)。注意,最后一個(gè)字段 TypeData 被注釋掉了,這說(shuō)明該處的結(jié)構(gòu)內(nèi)容根據(jù)不同的數(shù)據(jù)類(lèi)型有所不同。
TTypeKind 枚舉定義了可以使用 RTTI 信息的數(shù)據(jù)類(lèi)型,它幾乎包含了所有的 Delphi 數(shù)據(jù)類(lèi)型,其中包括 tkClass。
TTypeKind = (tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat, tkString, tkSet, tkClass, tkMethod, tkWChar, tkLString, tkWString, tkVariant, tkArray, tkRecord, tkInterface, tkInt64, tkDynArray);
TTypeData 是個(gè)巨大的記錄類(lèi)型,在此不再列出,后文會(huì)根據(jù)需要列出該記錄的內(nèi)容。
=============================================================================== ⊙ 獲取類(lèi)(class)的屬性(property)信息 ===============================================================================
這一段是 RTTI 中最復(fù)雜的部分,努力把本段吃透,后面的內(nèi)容都是非常簡(jiǎn)單的。
下面是一個(gè)獲取類(lèi)的屬性的例子:
procedure GetClassProperties(AClass: TClass; AStrings: TStrings); var PropCount, I: SmallInt; PropList: PPropList; PropStr: string; begin PropCount := GetTypeData(AClass.ClassInfo).PropCount; GetPropList(AClass.ClassInfo, PropList); for I := 0 to PropCount - 1 do begin case PropList[I]^.PropType^.Kind of tkClass : PropStr := '[Class] '; tkMethod : PropStr := '[Method]'; tkSet : PropStr := '[Set] '; tkEnumeration: PropStr := '[Enum] '; else PropStr := '[Field] '; end; PropStr := PropStr + PropList[I]^.Name; PropStr := PropStr + ': ' + PropList[I]^.PropType^.Name; AStrings.Add(PropStr); end; FreeMem(PropList); end;
你可以在表單上放置一個(gè) TListBox ,然后執(zhí)行以下語(yǔ)句觀(guān)察執(zhí)行結(jié)果:
GetClassProperties(TForm1, ListBox1.Items);
該函數(shù)先使用 GetTypeData 函數(shù)獲得類(lèi)的屬性數(shù)量。GetTypeData 是 TypInfo.pas 中的一個(gè)函數(shù),它的功能是返回 TTypeInfo 的 TypeData 數(shù)據(jù)的指針:
{ TypInfo.pas } function GetTypeData(TypeInfo: PTypeInfo): PTypeData; assembler;
class 的 TTypeData 結(jié)構(gòu)如下:
TTypeData = packed record case TTypeKind of tkClass: ( ClassType: TClass; // 類(lèi) (VMTptr) ParentInfo: PPTypeInfo; // 父類(lèi)的 RTTI 指針 PropCount: SmallInt; // 屬性數(shù)量 UnitName: ShortStringBase; // 單元的名稱(chēng) {PropData: TPropData}); // 屬性的詳細(xì)信息 end;
其中的 PropData 又是一個(gè)大小可變的字段。TPropData 的定義如下:
TPropData = packed record PropCount: Word; // 屬性數(shù)量 PropList: record end; // 占位符,真正的意義在下一行 {PropList: array[1..PropCount] of TPropInfo} end;
每個(gè)屬性信息在內(nèi)存中的結(jié)構(gòu)就是 TPropInfo,它的定義如下:
PPropInfo = ^TPropInfo; TPropInfo = packed record PropType: PPTypeInfo; // 屬性類(lèi)型信息指針的指針 GetProc: Pointer; // 屬性的 Get 方法指針 SetProc: Pointer; // 屬性的 Set 方法指針 StoredProc: Pointer; // 屬性的 StoredProc 指針 Index: Integer; // 屬性的 Index 值 Default: Longint; // 屬性的 Default 值 NameIndex: SmallInt; // 屬性的名稱(chēng)索引(以 0 開(kāi)始計(jì)數(shù)) Name: ShortString; // 屬性的名稱(chēng) end;
為了方便訪(fǎng)問(wèn)屬性信息,TypInfo.pas 中還定義了指向 TPropInfo 數(shù)組的指針:
PPropList = ^TPropList; TPropList = array[0..16379] of PPropInfo;
我們可以使用 GetPropList 獲得所有屬性信息的指針數(shù)組,數(shù)組用完以后要記得用 FreeMem 把數(shù)組的內(nèi)存清除。
{ TypInfo.pas } function GetPropList(TypeInfo: PTypeInfo; out PropList: PPropList): Integer;
GetPropList 傳入類(lèi)的 TTypeInfo 指針和 TPropList 的指針,它為 PropList 分配一塊內(nèi)存后把該內(nèi)存填充為指向 TPropInfo 的指針數(shù)組,最后返回屬性的數(shù)量。
上面的例子演示了如何獲得類(lèi)的所有屬性信息,也可以根據(jù)屬性的名稱(chēng)單獨(dú)獲得屬性信息:
{ TypInfo.pas } function GetPropInfo(TypeInfo: PTypeInfo; const PropName: string): PPropInfo;
GetPropInfo 根據(jù)類(lèi)的 RTTI 指針和屬性的名稱(chēng)字符串,返回屬性的信息 TPropInfo 的指針。如果沒(méi)有找到該屬性,則返回 nil。GetPropInfo 很容易使用,舉個(gè)例子:
ShowMessage(GetPropInfo(TForm, 'Name')^.PropType^.Name);
這句調(diào)用顯示了 TForm 類(lèi)的 Name 屬性的類(lèi)型名稱(chēng):TComponentName。
=============================================================================== ⊙ 獲取方法(method)的類(lèi)型信息 ===============================================================================
所謂方法就是以 of object 關(guān)鍵字聲明的函數(shù)指針,下面的函數(shù)可以顯示一個(gè)方法的類(lèi)型信息:
procedure GetMethodTypeInfo(ATypeInfo: PTypeInfo; AStrings: TStrings); type PParamData = ^TParamData; TParamData = record // 函數(shù)參數(shù)的數(shù)據(jù)結(jié)構(gòu) Flags: TParamFlags; // 參數(shù)傳遞規(guī)則 ParamName: ShortString; // 參數(shù)的名稱(chēng) TypeName: ShortString; // 參數(shù)的類(lèi)型名稱(chēng) 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;
作為實(shí)驗(yàn),在表單上放置一個(gè) TListBox,然后執(zhí)行以下代碼,觀(guān)察執(zhí)行結(jié)果:
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;
由于獲取方法的類(lèi)型信息比較復(fù)雜,我盡量壓縮代碼也還是有這么長(zhǎng),讓我們看看它的實(shí)現(xiàn)原理。GetMethodTypeInfo 的第一個(gè)參數(shù)是 PTypeInfo 類(lèi)型,表示方法的類(lèi)型信息地址。第二個(gè)參數(shù)是一個(gè)字符串列表,可以使用任何實(shí)現(xiàn) TStrings 操作的對(duì)象。我們可以使用 System.pas 中的 TypeInfo 函數(shù)獲得任何類(lèi)型的 RTTI 信息指針。TypeInfo 函數(shù)像 SizeOf 一樣,是內(nèi)置于編譯器中的。
GetMethodTypeInfo 還用到了 TypInfo.pas 中的 GetEnumName 函數(shù)。這個(gè)函數(shù)通過(guò)枚舉類(lèi)型的整數(shù)值得到枚舉類(lèi)型的名稱(chēng)。
function GetEnumName(TypeInfo: PTypeInfo; Value: Integer): string;
與獲取類(lèi)(class)的屬性信息類(lèi)似,方法的類(lèi)型信息也在 TTypeData 結(jié)構(gòu)中
TTypeData = packed record case TTypeKind of tkMethod: ( MethodKind: TMethodKind; // 方法指針的類(lèi)型 ParamCount: Byte; // 參數(shù)數(shù)量 ParamList: array[0..1023] of Char // 參數(shù)詳細(xì)信息,見(jiàn)下行注釋 {ParamList: array[1..ParamCount] of record Flags: TParamFlags; // 參數(shù)傳遞規(guī)則 ParamName: ShortString; // 參數(shù)的名稱(chēng) TypeName: ShortString; // 參數(shù)的類(lèi)型 end; ResultType: ShortString}); // 返回值的名稱(chēng) end;
TMethodKind 是方法的類(lèi)型,定義如下:
TMethodKind = (mkProcedure, mkFunction, mkConstructor, mkDestructor, mkClassProcedure, mkClassFunction, { Obsolete } mkSafeProcedure, mkSafeFunction);
TParamsFlags 是參數(shù)傳遞的規(guī)則,定義如下:
TParamFlag = (pfVar, pfConst, pfArray, pfAddress, pfReference, pfOut); TParamFlags = set of TParamFlag;
由于 ParamName 和 TypeName 是變長(zhǎng)字符串,不能直接取用該字段的值,而應(yīng)該使用指針步進(jìn)的方法,取出參數(shù)信息,所以上面的代碼顯得比較長(zhǎng)。
=============================================================================== ⊙ 獲取有序類(lèi)型(ordinal)、集合(set)類(lèi)型的 RTTI 信息 ===============================================================================
討論完了屬性和方法的 RTTI 信息之后再來(lái)看其它數(shù)據(jù)類(lèi)型的 RTTI 就簡(jiǎn)單多了。所有獲取 RTTI 的原理都是通過(guò) GetTypeData 函數(shù)得到 TTypeData 的指針,再通過(guò) TTypeInfo.TypeKind 來(lái)解析 TTypeData。任何數(shù)據(jù)類(lèi)型的 TTypeInfo 指針可以通過(guò) TypeInfo 函數(shù)獲得。
有序類(lèi)型的 TTypeData 定義如下:
TTypeData = packed record tkInteger, tkChar, tkEnumeration, tkSet, tkWChar: ( OrdType: TOrdType; // 有序數(shù)值類(lèi)型 case TTypeKind of case TTypeKind of tkInteger, tkChar, tkEnumeration, tkWChar: ( MinValue: Longint; // 類(lèi)型的最小值 MaxValue: Longint; // 類(lèi)型的最大值 case TTypeKind of tkInteger, tkChar, tkWChar: (); tkEnumeration: ( BaseType: PPTypeInfo; // 指針的指針,它指向枚舉的 PTypeInfo NameList: ShortStringBase; // 枚舉的名稱(chēng)字符串(不能直接取用) EnumUnitName: ShortStringBase)); // 所在的單元名稱(chēng)(不能直接取用) tkSet: ( CompType: PPTypeInfo)); // 指向集合基類(lèi) RTTI 指針的指針 end;
下面是一個(gè)獲取有序類(lèi)型和集合類(lèi)型的 RTTI 信息的函數(shù):
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;
在表單上放置一個(gè) TListBox,運(yùn)行以下代碼查看結(jié)果:
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;
(如果枚舉元素沒(méi)有按缺省的 0 基準(zhǔn)定義,那么將不能產(chǎn)生 RTTI 信息,為什么?)
=============================================================================== ⊙ 獲取其它數(shù)據(jù)類(lèi)型的 RTTI 信息 ===============================================================================
上面討論了幾個(gè)典型的 RTTI 信息的運(yùn)行,其它的數(shù)據(jù)類(lèi)型的 RTTI 信息的獲取方法與上面類(lèi)似。由于這些操作更加簡(jiǎn)單,就不一一討論。下面概述其它類(lèi)型的 RTTI 信息的情況:
LongString、WideString 和 Variant 沒(méi)有 RTTI 信息; ShortString 只有 MaxLength 信息; 浮點(diǎn)數(shù)類(lèi)型只有 FloatType: TFloatType 信息; TFloatType = (ftSingle, ftDouble, ftExtended, ftComp, ftCurr); Int64 只有最大值和最小值信息(也是 64 位整數(shù)表示); Interface 和動(dòng)態(tài)數(shù)組不太熟悉,就不作介紹了。
=============================================================================== ⊙ 結(jié)束 ===============================================================================
2007-3-31 0:28:40
2007-3-31 0:29:46
本文上篇基本上是 RTTI 入門(mén)介紹,續(xù)篇介紹了所有 TypInfo.pas 中的函數(shù),附加了 Classes.pas、Graphics.pas、Controls.pas 中的幾個(gè) RTTI 相關(guān)函數(shù)。對(duì)于關(guān)鍵函數(shù)的代碼提供匯編注釋。希望本文覆蓋了 Delphi 中 80% 的 RTTI 函數(shù)。時(shí)間倉(cāng)促,錯(cuò)誤難免,敬請(qǐng)批評(píng)指正。
本文排版格式為: 正文由窗口自動(dòng)換行;所有代碼以 80 字符為邊界;中英文字符以空格符分隔。
(作者保留對(duì)本文的所有權(quán)利,未經(jīng)作者同意請(qǐng)勿在在任何公共媒體轉(zhuǎn)載。)
目 錄 =============================================================================== ⊙ GetTypeData 函數(shù) ⊙ GetPropInfo 函數(shù) ⊙ FindPropInfo 函數(shù) ⊙ GetPropInfos 函數(shù) ⊙ SortPropList 函數(shù) ⊙ GetPropList 函數(shù) ------------------------------------------------------ ⊙ GetObjectPropClass 函數(shù) ⊙ PropType / PropIsType 函數(shù) ⊙ IsPublishedProp 函數(shù) ⊙ IsStoredProp 函數(shù) ⊙ FreeAndNilProperties 函數(shù) ⊙ SetToString / StringToSet 函數(shù) ⊙ GetEnumName / GetEnumValue / GetEnumNameValue 函數(shù) ------------------------------------------------------ ⊙ GetOrdProp 函數(shù)詳解 ⊙ SetOrdProp 函數(shù) ⊙ GetEnumProp / SetEnumProp 函數(shù) ⊙ GetSetProp / SetSetProp 函數(shù) ⊙ GetObjectProp / SetObjectProp 函數(shù) ⊙ GetStrProp / SetStrProp 函數(shù) ⊙ GetFloatProp / SetFloatProp 函數(shù) ⊙ GetPropValue / SetPropValue 函數(shù) ⊙ TPublishableVariantType class ------------------------------------------------------ ⊙ RegisterClass / FindClass 系列函數(shù) (Classes.pas) ⊙ IdentToInt / IntToIdent 系列函數(shù) (Classes.pas) ===============================================================================
正 文 =============================================================================== ⊙ GetTypeData 函數(shù) =============================================================================== GetTypeData 函數(shù)根據(jù) TTypeInfo 指針獲得 TTypeData 的地址。
function GetTypeData(TypeInfo: PTypeInfo): PTypeData; asm XOR EDX,EDX ; EDX 清零 MOV DL,[EAX].TTypeInfo.Name.Byte[0] ; 獲得 Name 字符串長(zhǎng)度 LEA EAX,[EAX].TTypeInfo.Name[EDX+1] ; 獲得 TTypeData 的地址 end;
=============================================================================== ⊙ GetPropInfo 函數(shù) =============================================================================== GetPropInfo 函數(shù)用于獲得屬性的 RTTI 指針 PPropInfo。它有四種重載形式,后面三種重載的實(shí)現(xiàn)都是調(diào)用第一種形式。AKinds 參數(shù)用于限制屬性的類(lèi)型,如果得到的 PPropInfo 不屬于指定的類(lèi)型,則返回 nil。
function GetPropInfo(TypeInfo: PTypeInfo; const PropName: string): PPropInfo;
function GetPropInfo(Instance: TObject; const PropName: string; AKinds: TTypeKinds = []): PPropInfo; function GetPropInfo(AClass: TClass; const PropName: string; AKinds: TTypeKinds = []): PPropInfo; function GetPropInfo(TypeInfo: PTypeInfo; const PropName: string; AKinds: TTypeKinds): PPropInfo;
=============================================================================== ⊙ FindPropInfo 函數(shù) =============================================================================== FindPropInfo 函數(shù)根據(jù)屬性名稱(chēng)獲得屬性的 RTTI 指針,它只是在 GetPropInfo 函數(shù)的基礎(chǔ)上加上了錯(cuò)誤檢查功能,如果沒(méi)有屬性 RTTI 信息,則觸發(fā) EPropertyError 異常。
function FindPropInfo(Instance: TObject; const PropName: string): PPropInfo; function FindPropInfo(AClass: TClass; const PropName: string): PPropInfo;
=============================================================================== ⊙ GetPropInfos 函數(shù) =============================================================================== GetPropInfos 函數(shù)的功能是把一個(gè)類(lèi)(class)所有屬性 RTTI 指針 PPropInfo 填充至傳入的參數(shù) PPropList 數(shù)組中。
注意:這個(gè)函數(shù)不負(fù)責(zé)分配該數(shù)組的內(nèi)容,使用前必須根據(jù)屬性的數(shù)量分配足夠的空間。該數(shù)組結(jié)束后必須清除分配的內(nèi)容。
procedure GetPropInfos(TypeInfo: PTypeInfo; PropList: PPropList);
注:使用 GetPropList 實(shí)現(xiàn)相同的功能更方便。
=============================================================================== ⊙ SortPropList 函數(shù) ==========================================
|