RTTI(一)プロフィール【転自大長者】

10169 ワード

第1部


 


RTTIの概要


クラス(class)とVMTの関係
クラス(class)、クラス(class of class)、クラス変数(class variable)の関係TObject.ClassTypeとTObject.ClassInfo isとas演算子の原理TTypeInfo–RTTI情報の構造取得クラス(class)の属性(property)情報
 

⊙RTTI概要


RTTI(Run-Time Type Information)が翻訳した名称は「ランタイムタイプ情報」であり、つまりランタイムにデータ型やクラス(class)の情報を得ることができる.このRTTIはいったい何の役に立つのか、今もはっきり言えません.Delphiの持続的なメカニズムを読むコードの中で多くのRTTIの運用を発見したので、まずRTTIを勉強するしかありません.次は私の学習ノートです.もし間違いを見つけたら教えてください.ありがとう!
DelphiのRTTIは主にクラス(class)のRTTIと一般データ型のRTTIに分けられ,以下クラス(class)から始まる.

⊙クラス(class)とVMTの関係


コンパイラの観点からVMTを指すポインタ(後述ではVMTptrで示す)であるクラス(class).クラスのVMTptrの負のアドレス方向にいくつかのクラス情報のポインタが格納され、これらのポインタの値とポインタが指す内容はコンパイル後に決定される.例えば、VMTptr-44の内容はクラス名(ClassName)へのポインタである.ただし、これらのクラス情報には数値を用いるアクセスするのではなく、Systemを介してアクセスするのが一般的である.pasで定義されたvmtで始まる定数(vtmClassName、vmtParentなど)にアクセスします.
クラスのメソッドには、オブジェクトレベルのメソッドとクラスレベルのメソッドの2つがあります.両者のSelfポインタの意味は異なる.オブジェクトレベルのメソッドでは、Selfはオブジェクトのアドレス空間を指すので、オブジェクトのメンバー関数にアクセスできます.クラス・レベルのメソッドでは、SelfはクラスのVMTを指すため、オブジェクトのメンバー・フィールドにはアクセスできず、VMT情報にのみアクセスできます.

⊙クラス(class)、クラスのクラス(class of class)、クラス変数(class variable)の関係


クラス(class)といえばVMTprです.Delphiではクラスのクラスをclass ofキーワードで定義することもでき、クラスのクラスを使用してクラス変数を定義することもできます.この3つの鍵を文法的に理解するのは難しくなく,クラスを普通のデータ型として考えればよい.コンパイラレベルではどうでしょうか.
議論を簡単にするために、上記の3つのタイプを表すために、TObject、TClass、およびTMyClassを使用します.
type

  TClass = class of TObject;

var

  TMyClass: TClass;

  MyObject: TObject;

begin

  TMyClass := TObject;

  MyObject := TObject.Create;

  MyObject := TClass.Create;

  MyObject := TMyClass.Create;

end;

上記の例では、3つのObjectオブジェクトが正常に作成されました.コンパイラの実装は、TObjectはVMTTR定数です.TClassもVMTpr定数であり、その値はTObjectである.TMyClassは、TObjectとして割り当てられたVMTptr変数です.TObject.CreateとTClass.Createのアセンブリコードはまったく同じです.しかし、TClassはデフォルトでクラスを表すだけでなく、クラスのタイプも表しており、クラス変数を定義し、いくつかのクラスレベルの操作を実現することができます.

⊙ TObject.ClassTypeとTObject.ClassInfo

function TObject.ClassType: TClass;

begin

  Pointer(Result) := PPointer(Self)^;

end;

TObject.ClassTypeはオブジェクトレベルのメソッドであり、Selfの値はオブジェクトメモリ空間へのポインタであり、オブジェクトメモリ空間の最初の4バイトはクラスのVMTptrである.したがって,この関数の戻り値はクラスのVMTprである.
class function TObject.ClassInfo: Pointer;

begin

  Result := PPointer(Integer(Self) + vmtTypeInfo)^;

end;

TObject.ClassInfoはclassキー定義を使用するため、クラスレベルのメソッドです.この方法のSelfポインタがVMTprである.したがって,この関数の戻り値はVMTpr負方向のvmtTypeInfoの内容である.
TObject.ClassInfoが返すPointerポインタは,実際にはクラスのRTTI構造を指すポインタである.でもTObjectにはアクセスできません.ClassInfoが指すコンテンツ(TObject.ClassInfoの戻り値は0)は、DelphiがTPersistentクラスおよびTPersistentの後継クラスでのみRTTI情報を生成するためである.(コンパイラの観点から、これはTPersistentクラスの宣言の前に{$M+}ポインタを使用した結果です.)TObjectはクラスRTTI情報を取得する関数もいくつか定義しており、以下に列挙すると、一つ一つ分析しない.
TObject.ClassName: ShortString;クラス名ClassParent: TClass;オブジェクトの親InheritsFrom: Boolean;クラスTObjectから継承するかどうか.InstanceSize: Longint;オブジェクトインスタンスのサイズ

⊙isとas演算子の原理


実行期間中にisキーワードを使用してオブジェクトがクラスに属しているかどうかを判断し、asキーワードを使用してオブジェクトを安全にクラスに変換できることを知っています.コンパイラの階層では、isとasの操作はSystemである.pasの2つの関数で完了しました.
{ System.pas } function _IsClass(Child: TObject; Parent: TClass): Boolean; begin   Result := (Child <> nil) and Child.InheritsFrom(Parent); end;
_IsClassは、オブジェクトがクラスまたはその親から継承されているかどうかを判断するために、TObjectのInheritsForm関数を使用します.各クラスのVMTには、クラスの親のVMTを指すvmtParentポインタがあります.TObject.InheritsFromは実際には[再帰]によって親VMTポインタが自分のVMTポインタに等しいか否かを判断し,そのクラスから継承されたか否かを判断する.
{ 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オペレータは実際にはSystemである.pasの_AsClass関数が完了しました.オブジェクトがクラスに属しているかどうかを判断するためにisオペレータを簡単に呼び出し、そうでなければ例外をトリガーします.しかしAsClassの戻り値はTObjectタイプですが、コンパイラは自動的に返されたオブジェクトをParentクラスに変更します.そうしないと、返されたオブジェクトはTObject以外のメソッドやデータを使用できません.
{ 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情報の構造


RTTI情報の構造定義はTypInfo.pas中:TTypeInfo=record//TTypeInfoはRTTI情報の構造Kind:TTypeKind;//RTTI情報のデータ型Name:ShortString;//データ型の名前
{Type Data:TTypeData}//RTTIの内容end;TTypeInfoはRTTI情報の構造である.TObject.ClassInfoは、class TTypeInfo情報を格納するポインタを返します.Kindは、RTTI構造に含まれるデータ型を表す列挙型である.Nameはデータ型の名前です.なお、最後のフィールドTypeDataは注釈されており、ここの構造内容がデータ型によって異なることを示している.
TTypeKind列挙は、RTTI情報を使用できるデータ型を定義し、tkClassを含むすべてのDelphiデータ型をほとんど含んでいる.
TTypeKind = (tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat,

tkString, tkSet, tkClass, tkMethod, tkWChar, tkLString, tkWString,

tkVariant, tkArray, tkRecord, tkInterface, tkInt64, tkDynArray);

TTypeDataは巨大なレコードタイプで、ここではリストされません.必要に応じて、後述します.

⊙クラスの属性情報を取得する


この段はRTTIの中で最も複雑な部分で、努力して本段を食べ抜いて、後の内容はすべてとても簡単です.次に、クラスのプロパティを取得する例を示します.
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;

フォームにTListBoxを配置し、次の文を実行して実行結果を確認できます.
GetClassProperties(TForm1, ListBox1.Items);

この関数は、GetTypeData関数を使用してクラスの属性数を取得します.GetTypeDataはTypInfo.pasの関数です.TTypeInfoのTypeデータを返すポインタです.
{ TypInfo.pas } function GetTypeData(TypeInfo: PTypeInfo): PTypeData; assembler; classのTTypeData構造は、TTypeData=packed record case TTypeKind of tkClass:(ClassType:TClass;//クラス(VMTptr)ParentInfo:PPType Info;//親のRTTIポインタPropCount:SmallInt;//属性数UnitName:ShortStringBase;//ユニット名{PropData:TPropData});//属性の詳細end;
その中のPropDataはまた1つの大きさの可変なフィールドです.TPropDataの定義は以下の通りである.
TPropData = packed record

  PropCount: Word; //  

  PropList: record

  end; //  , 

  {PropList: array[1..PropCount] of TPropInfo}

end;

各属性情報のメモリ内の構造はTPropInfoであり、その定義は以下の通りである.
PPropInfo = ^TPropInfo;

TPropInfo = packed record

  PropType: PPTypeInfo; //  

  GetProc: Pointer; //   Get  

  SetProc: Pointer; //   Set  

  StoredProc: Pointer; //   StoredProc  

  Index: Integer; //   Index  

  Default: Longint; //   Default  

  NameIndex: SmallInt; //  (  0  )

  Name: ShortString; //  

end;

属性情報へのアクセスを容易にするため、TypInfo.pasでは、TPropInfo配列へのポインタも定義されています.
PPropList = ^TPropList;

TPropList = array[0..16379] of PPropInfo;

GetPropListを使用してすべての属性情報のポインタ配列を取得することができます.配列が切れたら、FreeMemで配列のメモリをクリアすることを忘れないでください.
{ TypInfo.pas } function GetPropList(TypeInfo: PTypeInfo; out PropList: PPropList): Integer;
GetPropListはクラスのTTypeInfoポインタとTPropListのポインタを入力し、PropListにメモリを割り当てた後、そのメモリをTPropInfoを指すポインタ配列に充填し、最後に属性の数を返す.
上記の例では、クラスのすべての属性情報を取得する方法と、属性名に基づいて個別に属性情報を取得する方法を示します.
{ TypInfo.pas } function GetPropInfo(TypeInfo: PTypeInfo; const PropName: string): PPropInfo;
GetPropInfoはクラスのRTTIポインタと属性の名前文字列に基づいて,属性の情報TPropInfoのポインタを返す.このプロパティが見つからない場合はnilを返します.GetPropInfoは使いやすいです.例を挙げます.
ShowMessage(GetPropInfo(TForm, 'Name')^.PropType^.Name);

この呼び出しには、TFOrmクラスのNameプロパティのタイプ名:TComponentNameが表示されます.