Swanman's Horizon

性欲をもてあましつつなんらかの話をするよ。

Delphiでリフレクションもどき その2 メソッド名の取得

Delphi2010で強化されたRTTIを使えばメソッド名の取得なんて超簡単なんだろうけど、まぁそれはそれで。
メソッド名の取得は、

  • publishedなメソッドであること
  • 専用の関数などがなくUndocumentedな力技であること

の2点を除けばわりとシンプルで汎用的に使える。
とりあえずコード。

procedure EnumMethods(Target: TObject);
var
  ref: TClass;
  table: PChar;
  count: Word;
  method: TMethod;
begin
  ref := Target.ClassType;
  while ref <> nil do
  begin
    table := PPointer(PChar(ref) + vmtMethodTable)^;
    if Assigned(table) then
    begin
      count := PWord(table)^;
      Inc(table, SizeOf(Word));
      while count > 0 do
      begin
        Writeln(PShortString(table + SizeOf(Word) + SizeOf(Pointer))^); // メソッド名を出力
        Inc(table, PWord(table)^);
        Dec(count);
      end;
    end;
    ref := ref.ClassParent;
  end;
end;

どこぞに投稿したコードほとんどそのままという部分は置いといて中身の説明をすると、まずClassTypeでクラス参照を取得する。例えば渡されたのがTFontのインスタンスならTFontというクラスそのものへの参照が返る。
ここまでは用意されたメソッドを使ってるけど、その次のvmtMethodTableって辺りから力技っぽくなり出して、この定数の名前通りメソッドテーブルへの参照を得る。
そして最後にテーブル内にある各メソッド名の位置をtable + SizeOf(Word) + SizeOf(Pointer)によって計算してるんだけど、SizeOf(...)はぶっちゃけマジックナンバを減らす目的だけにやってて、こんなバージョン依存の方法で気を遣う必要もないのでtable+5で問題ないと思う。とにかく+5した位置に名前があるとだけ覚えておけばきっとOK。
正直このコードとこの説明だけじゃさっぱりわかんねってなるだろうけど、要は上に挙げた条件を満たした上でコンパイルするとメソッド名がメタデータとしてバイナリに埋め込まれるから、それを取り出すためのコードと考えればいい。実際にコンパイルしてみて、バイナリエディタで開いて(メソッド名で検索して大方の位置を特定しつつ)メタデータの周辺を見るとわかりやすい(はず)。

さらにあとひとつ条件を付け加えると、メソッド名だけではなく引数の名前や引数の型、引数がconstかvarかout渡しかなんてことまで分かったりもするけど、それはまた次回。