delphi 是否可以从引发异常的点恢复执行?

ktecyv1j  于 11个月前  发布在  其他
关注(0)|答案(6)|浏览(142)

有一个例子可以说明我的问题:

procedure Test;
begin
  try
    ShowMessage('1');
    raise EWarning.Create(12345, 'Warning: something is happens!');
    ShowMessage('2');
  except
    on E: EWarning do
      if IWantToContinue(E.ErrorCode) then
        E.SkipThisWarning // shows '1' then '2'
      else
        E.StopExecution;  // shows '1'
  end;
end;

function IWantToContinue(const ErrorCode: Integer): Boolean;
begin
  //...
end;

字符串
我试着用这样的东西:

asm
  jmp ExceptAddr
end;


但没用的。
有什么想法吗?
谢谢.

hec6srdp

hec6srdp1#

不,这是不可能的:

有两种异常:由程序员使用raise命令引发的逻辑异常,以及由CPU在各种情况下引发的外部异常:被零除、堆栈溢出、访问冲突。对于第一种,即逻辑异常,您无能为力,因为它们是应用程序“流程”的一部分。您不能打乱第三方代码的流程,您甚至不能打乱自己的代码流。

外部异常

当一条指令失败时,这些异常通常是由于运行一条CPU指令而产生的。在 Delphi 中,这些异常作为EExternal的后代可用。列表中包括访问冲突、被零除、堆栈溢出、特权指令和其他不多的异常。理论上,对于这些异常中的一些,可以删除引起异常的条件,并重试该条CPU指令。允许原始代码像没有发生错误一样继续运行。2例如,一些访问冲突可能通过在发生错误的地址实际Map一页RAM来“修复”。
在SEH中有规定(Structured Exception Handling)机制来处理这种可重试的错误, Delphi 也在使用SEH。不幸的是,Delphi没有公开所需的元素来使其易于访问,因此使用它们将非常困难,如果不是不可能的话。尽管如此,对于特定类型的EExternal错误,聪明的Delphinians可能会尝试编写自定义SEH代码并使其正常工作。如果是这种情况,请提出一个新问题,说明您遇到的特定类型的错误以及您要采取的步骤来删 debugging 误条件:您将得到一些工作代码或自定义的解释,说明您的想法为什么不起作用。

通过使用raise启动的逻辑异常

大多数异常都属于这一类,因为大多数代码在执行潜在危险的低级操作之前都会检查其输入。例如,当试图访问TList中的无效索引时,在试图访问所请求的索引之前,会检查该索引并引发无效索引异常。如果不进行检查,访问无效索引将返回无效数据或引发访问冲突。这两种情况都很难跟踪错误,因此无效索引异常是一件非常好的事情。为了回答这个问题,即使允许代码访问无效的索引,从而导致访问冲突,也不可能“修复”代码并继续,因为无法猜测正确的索引应该是什么。
换句话说,修复“逻辑”异常不起作用,也不应该起作用,而且非常危险。如果引发错误的代码是你的代码,那么你可以简单地将其重组为不引发警告异常。如果不是你的代码,那么继续处理异常福尔斯“非常危险”的范畴(更不用说这在技术上是不可能的)当查看已经编写好的代码时,问问自己:如果raise ExeptionShowMessage替换,代码的行为会正常吗?答案应该大多是"NO, the code would fail anyway"。对于非常罕见的、非常错误的第三方代码,在没有好的理由的情况下引发异常,您可以在运行时请求有关修补代码的特定帮助,以“从不”引发异常。
以下是一些第三方代码中可能包含的内容:

function ThirdPartyCode(A, B: Integer): Integer;
begin
  if B = 0 then raise Exception.Create('Division by zero is not possible, you called ThirdPartyCode with B=0!');
  Result := A div B;
end;

字符串
很明显,在异常之后继续那个代码是不会让东西“自我修复”的。
协力厂商程式码也可能如下所示:

procedure DoSomeStuff;
begin
  if SomeCondition then
    begin
      // do useful stuff
    end
  else
    raise Exception.Create('Ooops.');
end;


代码将在哪里“继续”?很明显不是"do usefull stuff"部分,除非代码是专门这样设计的。
当然,这些都是一些简单的例子,只是触及了表面。从技术的Angular 来看,在异常之后“继续”,就像你建议的那样,要比跳转到错误的地址困难得多。方法调用使用堆栈空间来设置局部变量。这些空间是在错误之后“回滚”的过程中释放的。finally块被执行,可能在需要的地方释放了资源。跳回原始地址是非常错误的,因为调用代码在堆栈上不再有它所期望的,它的局部变量不再是它们所期望的。

如果是您的代码引发了异常

你的代码可以很容易地被修复。使用类似这样的方法:

procedure Warning(const ErrorText:string);
begin
  if not UserWantsToContinue(ErrorText) then
    raise Exception.Create(ErrorText);
end;

// in your raising code, replace:
raise Exception.Create('Some Text');

// with:
Warning('Some Text');

pqwbnv8z

pqwbnv8z2#

不,你必须重新构造你的代码,

procedure Test;
begin
  ShowMessage('1');
  try
    raise EWarning.Create(12345, 'Warning: something is happens!');
  except
    on E: EWarning do
      if IWantToContinue(E.ErrorCode) then
        // shows '1' then '2'
      else
        raise; // shows '1'
  end;
  ShowMessage('2');
end;

字符串

5ssjco0h

5ssjco0h3#

在C++中,可以使用SEH __try/__except块,其__except表达式的计算结果为EXCEPTION_CONTINUE_EXECUTION
在 Delphi 中,不可能直接使用SEH,AFAIK。

4ktjp1zp

4ktjp1zp4#

基本上,您的代码无法工作,因为ExceptAddr是一个函数,而不是一个变量。因此,您的代码片段更改如下:

{$O-}
procedure TForm1.FormCreate(Sender: TObject);
begin
  try
    OutputDebugString('entering');
    raise Exception.Create('Error Message');
    OutputDebugString('leaving');
  except on E: Exception do
    begin
      OutputDebugString(PChar(Format('ExceptAddr = %p', [ExceptAddr])));
      asm
        CALL  ExceptAddr
        JMP   EAX
      end;
    end;
  end;
end;

字符串

tzxcd3kk

tzxcd3kk5#

..你可以嵌套多个try-except,并在每个异常中确定是否继续:

try
  try
    some code
  except
    ret := message('want to continue?');
    if ret <> mrYes then
      exit;
  end;
  some other code to perform when no exception or user choose to continue
except
  etc..
end;

字符串

hs1rzwqc

hs1rzwqc6#

这是我使用的例子。下面的过程将从几个URL下载文件(已经是数据集形式)。问题是http经常有问题,所以需要重试机制来尝试再次下载。在这个例子中我将尝试3次,每次我给给予5秒睡眠。重试3次后比将Break;
CDS是TClientDataset Memo是TMemo mnuDownload是TButton或菜单

uses IDHTTP;

procedure TForm1.mnuDownloadClick(Sender: TObject);
var
  S: String;
  HTTP: TIdHTTP;
  Stream: TMemoryStream;
  iRetry: Integer;

  function DoLoad_HttpXml: Boolean;
  begin
    try
      Result := True;
      HTTP.Get(CDS.FieldByName('url').AsString, Stream);
      Stream.SaveToFile(DataMod.XmlTarget +'\'+ S + IntToStr(CDS.RecNo)+ '.xml');
    except
      on E: Exception do begin
        inc(iRetry);
        Sleep(5000);
        Result := False;
        Memo.Lines.Add('File Number: '+IntToStr(CDS.RecNo)+' will Retry in 5 secs ( Attempt : '+IntToStr(iRetry)+' of 3 )');
      end;
    end;
  end;

begin
  mnuDownload.Enabled := False;
  HTTP := nil;
  Stream := nil;
  iRetry := 0;
  S := FormatDateTime('yyyy-mm-dd hh-mm-', Now);

  Memo.Clear;
  CDS.First;
  HTTP := TIdHTTP.Create(nil);
  HTTP.HandleRedirects := True;
  Stream := TMemoryStream.Create();

  while not CDS.Eof do begin
    Application.ProcessMessages;
    if iRetry = 3 then Break;
    if DoLoad_HttpXml then begin
      CDS.Edit;
      CDS.FieldByName('status').Value := True;
      CDS.Post;
      CDS.Next;
    end;
  end;

  if Assigned(HTTP) then FreeAndNil(HTTP);
  if Assigned(Stream) then FreeAndNil(Stream);
  mnuDownload.Enabled := True;
  CDS.First;
end;

字符串

相关问题