4

メソッド トレース用の次の典型的なコードを考えてみましょう (説明のために簡略化しています)。

type
  IMethodTracer = interface
  end;

  TMethodTracer = class(TInterfacedObject, IMethodTracer)
  private
    FName: String;
    FResultAddr: Pointer;
    FResultType: PTypeInfo;
  public
    constructor Create(
      const AName: String;
      const AResultAddr: Pointer = nil;
      const AResultType: PTypeInfo = nil);
    destructor Destroy; override;
  end;

constructor TMethodTracer.Create(
  const AName: String;
  const AResultAddr: Pointer;
  const AResultType: PTypeInfo);
begin
  inherited Create();
  FName := AName;
  FResultAddr := AResultAddr;
  FResultType := AResultType;
  Writeln('Entering ' + FName);
end;

destructor TMethodTracer.Destroy;
var
  lSuffix: String;
  lResVal: TValue;
begin
  lSuffix := '';
  if FResultAddr <> nil then
    begin
      //there's probably a more straight-forward to doing this, without involving TValue:
      TValue.Make(FResultAddr, FResultType, lResVal);
      lSuffix := ' - Result = ' + lResVal.AsString;
    end;
  Writeln('Leaving ' + FName + lSuffix);

  inherited Destroy;
end;

function TraceMethod(
  const AName: String;
  const AResultAddr: Pointer;
  const AResultType: PTypeInfo): IMethodTracer;
begin
  Result := TMethodTracer.Create(AName, AResultAddr, AResultType);
end;

//////

function F1: String;
begin
  TraceMethod('F1', @Result, TypeInfo(String));
  Writeln('Doing some stuff...');
  Result := 'Booyah!';
end;

F1();

これは意図したとおりに機能しています。出力は次のとおりです。

F1に入る
何かをしている...
F1を去る - 結果= Booyah!

への呼び出しに必要なパラメーターの数を最小限に抑える方法を探しています。理想的には、-関連の引数を完全TraceMethod()にスキップできるようにします。Result私はアセンブラやスタックレイアウトの経験はありませんが、間違っていなければ、他の人が見た「魔法」から判断すると、少なくとも暗黙の魔法のメモリアドレスResult-変数は何らかの方法で取得できるはずです。 ? そして、そこから型情報を取得することもできますか?

もちろん、引数をTraceMethod完全に渡す必要がなくなる「周囲の」関数自体の名前を特定することさえできれば...

Delphi XE2 を使用しているため、最近導入されたすべての言語/フレームワーク機能を使用できます。

そして、誰かが言及する前に: 私の実際のコードは既に-callsの代わりにCodeSite.EnterMethod/を使用しています。また、この単純化された例は複雑な型を処理できず、エラー処理をまったく実行しないことも認識しています。ExitMethodWriteln

4

2 に答える 2

3

あなたの最善の策は、ただ合格すること@Resultです。そうしないと、アドレスResultあるという保証さえありません。などの単純な型を返す関数は、結果を EAX レジスタに入れます。結果にアドレスが含まれる理由がない場合、コンパイラはそれにメモリを割り当てません。式を使用すると、コンパイラは強制的にアドレスを指定します。IntegerBoolean@Result

ただし、アドレスを知っているだけでは、戻り値の型はわかりません。RTTIで発見する方法があるかもしれません。これには、次の 3 つの手順が含まれます。

  1. メソッド名からクラス名を抽出します。次に、そのタイプの RTTI を取得できます。これには、メソッド名にクラスの明確な名前 (ユニット名を含む) を含める必要があります。

  2. そのタイプのメソッドのリストを使用して、メソッドの RTTI を見つけます。名前がメソッドを一意に識別するとは限らないため、これは複雑になります。オーバーロードはすべて同じ名前で表示されます。(Rruz は、オーバーロードされたメソッドの RTTI をメソッドのコンテキストで処理する方法を示しましたInvoke。) また、デバッグ情報から取得したメソッド名は、必ずしも RTTI 名と一致するとは限りません。

    名前を一致させようとする代わりに、すべてのクラスのメソッドをループして、CodeAddressプロパティが呼び出し元のアドレスと一致するメソッドを検索することができます。ただし、(戻りアドレスではなく) 呼び出し元の開始アドレスを取得する方法を決定することは、予想以上に難しいことがわかっています。

  3. メソッドの戻り値の型を取得し、プロパティを使用しHandleて最終的にPTypeInfo必要な値を取得します。

于 2012-05-30T17:15:16.053 に答える
1

この機能の一部は、Delphi 5 から XE2 まで動作するTSynLogクラスにすでに含まれています。

これは完全なオープン ソースであり、Delphi XE2 で動作するため、必要なすべてのソース コードが手元にあります。また、例外インターセプトとスタック トレースを備えています。

次のようなトレースをコーディングできます。

procedure TestPeopleProc;
var People: TSQLRecordPeople;
    Log: ISynLog;
begin
  Log := TSQLLog.Enter;
  People := TSQLRecordPeople.Create;
  try
    People.ID := 16;
    People.FirstName := 'Louis';
    People.LastName := 'Croivébaton';
    People.YearOfBirth := 1754;
    People.YearOfDeath := 1793;
    Log.Log(sllInfo,People);
  finally
    People.Free;
  end;
end;

次のようにログに記録されます。

20120520 13172261  +    000E9F67 SynSelfTests.TestPeopleProc (784)
20120520 13172261 info      {"TSQLRecordPeople(00AB92E0)":{"ID":16,"FirstName":"Louis","LastName":"Croivébaton","Data":"","YearOfBirth":1754,"YearOfDeath":1793}}
20120520 13172261   -    000EA005 SynSelfTests.TestPeopleProc (794) 00.002.229

あれは:

  • 行番号とメソッド名があります。
  • メソッド内で消費された正確な時間 (00.002.229) があります。
  • 任意の結果を表示するのは非常に簡単です (ここでは、クラスは JSON として直接シリアル化されており、任意の種類のデータ (レコードを含む) に対しても同じことができます)。
  • メソッドはネストできます。また、ログ ビューアで使用できるプロファイリング ツールもあります。つまり、ログ ファイルから、顧客側でどのメソッドに最も多くの時間が費やされたかを取得できます。
  • スタック トレースや必要な情報を簡単に追加できます。
  • 私たちのコードは速度に関して非常に最適化されています。たとえば、interfaceあなたと同じように「auto-leave」関数に を使用しますが、「偽の参照カウント」トリックを使用するため、メモリ割り当ては行いません。
  • もちろん、Logスタックに変数を定義することはオプションです。メソッドの Enter/Leave をトレースするだけであれば、記述するだけTSQLLog.Enterで十分です。Logローカル変数は、メソッド内で追加情報を簡単にネストするために使用されます ( ) Log.Log(sllInfo,People)

デバッグ情報 (つまり、メソッド名と行番号) は.map、コンパイル時に生成されたファイルの独自の非常に最適化されたバイナリ変換から取得されることに注意してください。それ自体よりもはるかに小さいため.map(たとえば、900 KB .map-> exe 内に簡単に埋め込むことができる 70 KB .mab)、したがって、JCL または MadExcept で使用される形式よりも小さく、コンパイル時に埋め込まれた情報よりも小さくなります。デルファイ。

「Enter」メソッド内に「結果」をハードコーディングする価値があるとは思いません。多くのコーディングが追加されますが (たとえば、への変換にTValueは時間がかかります)、ほとんどの場合、結果の内容よりも多くのことを知る必要があります。

于 2012-05-30T17:14:58.117 に答える