当前位置 : 主页 > 编程语言 > delphi >

Delphi Assembler / RTTI-gurus:我可以获取函数中隐含的Result变量的内存地址和类型信

来源:互联网 收集:自由互联 发布时间:2021-06-23
考虑这种典型的方法跟踪代码(简化说明): type IMethodTracer = interface end; TMethodTracer = class(TInterfacedObject, IMethodTracer) private FName: String; FResultAddr: Pointer; FResultType: PTypeInfo; public constructor Cre
考虑这种典型的方法跟踪代码(简化说明):

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();

这是按预期工作的.输出是:

Entering F1
Doing some stuff…
Leaving F1 – Result = Booyah!

我现在正在寻找一种方法来最小化调用TraceMethod()所需的参数数量,理想情况下允许我完全跳过与Result相关的参数.我自己没有使用汇编程序或堆栈布局的经验但是如果我没有弄错,从我见过的其他人所做的“魔法”判断,至少隐含的魔法Result-variable的内存地址应该可以以某种方式获得,不应该它?也许有人可以从那里得到它的类型信息吗?

当然,如果有可能甚至可以确定“周围”函数本身的名称,这将消除完全将参数传递给TraceMethod的需要……

我正在使用Delphi XE2,因此可以使用所有最近引入的语言/框架功能.

在任何人提到它之前:我的实际代码已经使用CodeSite.EnterMethod / ExitMethod而不是Writeln调用.我也知道这个简化的例子不能处理复杂的类型,也不会执行任何错误处理.

你最好的选择是真正传递@Result.如果不这样做,则无法保证Result甚至有地址.返回简单类型(如Integer和Boolean)的函数将结果放入EAX寄存器.如果没有理由让结果有地址,那么编译器就不会为它分配任何内存.使用@Result表达式强制编译器为其指定地址.

但是,仅仅知道地址不会得到返回类型.可能有一种方法可以通过RTTI发现它.这将涉及三个步骤:

>从方法名称中提取类名.然后你可以get the RTTI for that type.这将要求方法名称包含类的明确名称(包括单位名称).
>使用该类型的list of methods,找到该方法的RTTI.由于名称不一定唯一地标识方法,因此这将变得复杂.重载都将显示相同的名称. (Rruz在Invoke方法的上下文中显示了how to deal with RTTI of overloaded methods.)此外,从调试信息中获得的方法名称不一定与RTTI名称匹配.

您可以改为遍历所有类的方法,而不是尝试匹配名称,搜索其CodeAddress属性与调用方地址匹配的方法.然而,确定如何获得调用者的起始地址(而不是返回地址)比我预期的更难找到.
>获取the method’s return type并使用Handle属性最终获得所需的PTypeInfo值.

网友评论