在Delphi中,`at ReturnAddress`是什么意思?

31

在浏览 System.Zip(Delphi XE2)以了解其工作原理时,我发现了这个函数:

procedure VerifyWrite(Stream: TStream; var Buffer; Count: Integer);
begin
  if Stream.Write(Buffer, Count) <> Count then
    raise EZipException.CreateRes(@SZipErrorWrite) at ReturnAddress;
end;

我有点困惑的是at ReturnAddress这部分。

我不知道at是一个有效的关键字(语法高亮似乎也没有识别它)。

根据IDE,它被声明为System.ReturnAddress,但我只能在procedure _HandleAnyException;的(asm)代码中找到它被声明为标签。尽管系统单元充满了对它的引用。

所以我想知道的是:

  1. ReturnAddress是什么?
  2. Raise Exception.Create ... at ReturnAddress具体是做什么的?

如果您能给出一个现实世界的例子,在那里使用这个结构将会非常有用,或者如果您可以建议不要使用它,那么就能获得额外的奖励分数。

2个回答

24

ReturnAddressVerifyWrite执行完毕后将返回的地址。

Raise Exception.Create... at ReturnAddress意味着当异常对话框显示时,它将指示异常的地址为ReturnAddress。换句话说,异常消息将显示为Exception <whatever> raised at <ReturnAddress>: <Exception Message>

以下是Delphi 7帮助文件的摘录。它与在线版本几乎相同。

 

要引发异常对象,请使用具有raise语句的异常类实例。例如:

raise EMathError.Create;

通常,raise语句的格式为

raise object at address

对象和地址都是可选的;请参见重新引发异常。当指定了一个地址时,它可以是任何求值为指向指针类型的表达式,但通常是指向过程或函数的指针。例如:

raise Exception.Create('Missing parameter') at @MyFunction;

使用此选项可以在比实际发生错误的位置更早的堆栈点上引发异常。

特别注意最后一句话,它非常具体地说明了at <address>的使用方法。


@ain:感谢你的格式调整帮助。我不是要删除你编辑的事实,只是想强调引用文本的最后一句话。 :) - Ken White
6
这种语法结构的真实世界用途通常是为了使用“辅助函数”来引发异常。例如,在VCL中,有一个名为TList.Error的函数,其中包含所有与TList相关的错误。知道异常是在该函数中引发的对调试没有帮助,因此它使用at语法将异常地址放回调用Error函数的函数中,这样当您在映射文件中查找地址时,就可以更好地了解罪犯是谁。(而且为什么要使用助手函数?首先,它使调用方的代码生成更简单。) - Rob Kennedy
1
@RobKennedy:调用堆栈不会显示相同的信息吗? - afrazier
2
如果你有调用堆栈,@Afrazier,那当然可以。但是at语法自1995年以来就存在了。当时,MadExcept还没有出现,无法为您提供来自客户的漂亮崩溃报告。 - Rob Kennedy

10

ReturnAddr在之前的Delphi版本中不是一个谜题。考虑下一个测试(Delphi XE):

procedure RaiseTest1;

  procedure RaiseException(ReturnAddr: Pointer);
  begin
    raise Exception.Create('OOPS!') at ReturnAddr;
  end;

asm
      POP    EAX
      JMP    RaiseException
end;

procedure RaiseTest2;
begin
  raise Exception.Create('OOPS!');
end;


procedure TForm1.Button3Click(Sender: TObject);
begin
  RaiseTest1;
end;

procedure TForm1.Button4Click(Sender: TObject);
begin
  RaiseTest2;
end;

如果您在调试器下按下Button3并在异常消息框中按下“中断”按钮,调试器会停在以下位置:

procedure TForm1.Button3Click(Sender: TObject);
begin
  RaiseTest1; // <-- here
end;

如果您按下Button4,调试器会停在此处。
procedure RaiseTest2;
begin
  raise Exception.Create('OOPS!');  // <-- here
end;

正如您所看到的,RaiseTest1修改了默认异常堆栈帧,并使调试变得更加简单,因为RaiseTest1(2)过程的唯一目的是引发异常。

我想XE2中有些变化,以至于ReturnAddr语法得到简化。


我不明白您认为哪个语法发生了改变。 - Rob Kennedy
1
@RobKennedy 我认为他的意思是从ReturnAddrReturnAddress,但我找不到任何相关文档。但似乎ReturnAddr是系统单元中嵌套函数的名称(请参见此错误)。 - ventiseis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接