为什么异常没有被try ...除了end;捕获? [英] Why the exception is not caught by the try... except end;?

查看:68
本文介绍了为什么异常没有被try ...除了end;捕获?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下代码(可在iOS下与Delphi Tokyo一起运行):

I have this code (that runs under iOS with Delphi Tokyo):

procedure TMainForm.Button1Click(Sender: TObject);
var aData: NSData;
begin    
  try    
      try
        aData := nil;
      finally
        // this line triggers an exception
        aData.release;
      end;    
  except
    on E: Exception do begin
      exit;
    end;
  end;

end;

通常情况下,例外应捕获在末尾块,但在这种情况下,它不会被处理程序捕获,并传播到 Application.OnException 处理程序。

Normally the exception should be caught in the except end block, but in this case it is not caught by the handler and it is propagated to the Application.OnException handler.


地址0000000100EE9A8C的访问冲突,访问地址
0000000000000000

Access violation at address 0000000100EE9A8C, accessing address 0000000000000000

我错过了什么吗?

推荐答案

这是一个 bug (实际上,这是iOS和Android平台上的功能)(可能在具有LLVM后端的其他系统上-尽管没有明确记录)。

This is a bug (actually, a feature) on iOS and Android platforms (possibly on others with LLVM backend - though they are not explicitly documented).

核心问题是由 nil 引用上的虚拟方法调用引起的异常构成了硬件异常,该硬件异常未被最近的异常处理程序捕获,并且被传播到下一个异常处理程序(在这种情况下,传播到Application

Core issue is that exception caused by virtual method call on nil reference constitutes hardware exception that is not captured by nearest exception handler and it is propagated to the next exception handler (in this case to Application exception handler).

在try-except块中使用函数调用来防止未捕获的硬件异常


使用适用于iOS设备的编译器,只有在try块包含方法或函数调用的情况下,块才能捕获硬件
异常。
这是与编译器的LLVM后端有关的差异,如果在try块中未调用任何方法/函数,则
不会返回。

With compilers for iOS devices, except blocks can catch a hardware exception only if the try block contains a method or function call. This is a difference related to the LLVM backend of the compiler, which cannot return if no method/function is called in the try block.

在iOS和Android平台上显示此问题的最简单代码是:

The simplest code that exhibits the issue on iOS and Android platform is:

var
  aData: IInterface;
begin
  try
    aData._Release;
  except
  end;
end;

在Windows平台上执行上述代码可以按预期工作,并且异常被异常处理程序捕获。上面的代码中没有 nil 分配,因为 aData 是接口引用,并且在所有平台上编译器都会自动清除它们。添加 nil 分配是多余的,并且不会更改结果。

Executing above code on Windows platform works as expected and the exception is caught by exception handler. There is no nil assignment in above code, because aData is interface reference and they are automatically nilled by compiler on all platforms. Adding nil assignment is redundant and does not change the outcome.

显示异常是由虚拟方法调用引起的

To show that exceptions are caused by virtual method calls

type
  IFoo = interface
    procedure Foo;
  end;

  TFoo = class(TInterfacedObject, IFoo)
  public
    procedure Foo; virtual;
  end;

procedure TFoo.Foo;
var
  x, y: integer;
begin
  y := 0;
  // division by zero causes exception here
  x := 5 div y;
end;

在以下所有代码变体中,异常转义为异常处理程序。

In all following code variants, exception escapes exception handler.

var
  aData: IFoo;
begin
  try
    aData.Foo;
  except
  end;
end;

var
  aData: TFoo;
begin
  try
    aData.Foo;
  except
  end;
end;

即使我们更改 Foo 方法的实现并

Even if we change Foo method implementation and remove all code from it, it will still cause escaping exception.

如果我们更改 Foo 从虚拟到静态的声明,除数为零引起的异常将被正确捕获,因为允许在 nil 引用上调用静态方法,并且调用本身不允许抛出任何异常-从而构成文档中提到的函数调用。

If we change Foo declaration from virtual to static, exception caused by division to zero will be properly caught because call to static methods on nil references is allowed and call itself does not throw any exceptions - thus constitutes function call mentioned in documentation.

type
  TFoo = class(TInterfacedObject, IFoo)
  public
    procedure Foo; 
  end;

  TFoo = class(TObject)
  public
    procedure Foo; 
  end;

另一个也会导致正确处理异常的静态方法变体是声明 x 作为 TFoo 类字段,并以 Foo 方法访问该字段。

Another static method variant that also causes exception that is properly handled is declaring x as TFoo class field and accessing that field in Foo method.

  TFoo = class(TObject)
  public
    x: Integer;
    procedure Foo; 
  end;

procedure TFoo.Foo;
var
  x: integer;
begin
  x := 5;
end;






返回到原始问题 NSData 参考。 NSData 是Objective-C类,它们在Delphi中表示为接口。


Back to the original question that involved NSData reference. NSData is Objective-C class and those are represented as interfaces in Delphi.

  // root interface declaration for all Objective-C classes and protocols
  IObjectiveC = interface(IInterface)
    [IID_IObjectiveC_Name]
  end;

由于接口引用上的调用方法始终是通过VMT表的虚拟调用,因此在这种情况下与直接在对象引用上调用的虚拟方法调用类似的方式(存在相同的问题)。该调用本身会引发异常,并且不会被最近的异常处理程序捕获。

Since calling methods on interface reference is always virtual call that goes through VMT table, in this case behaves in similar manner (exhibits same issue) as virtual method call invoked directly on object reference. The call itself throws an exception and is not caught by nearest exception handler.

解决方法:

其中引用可能为 nil 的代码中的一种变通办法是检查是否为 nil ,然后在其上调用虚拟方法。如果需要,在 nil 引用的情况下,我们还可以引发常规异常,该异常可以通过封装异常处理程序来正确捕获。

One of the workarounds in code where reference might be nil is checking it for nil before calling virtual method on it. If needed, in case of nil reference we can also raise regular exception that will be properly caught by enclosing exception handler.

var
  aData: NSData;
begin
  try
    if Assigned(aData) then
      aData.release
    else
      raise Exception.Create('NSData is nil');
  except
  end;
end;

文档中提到的另一种解决方法是将代码放入附加函数(方法)中

Another workaround as mentioned in documentation is to put code in additional function (method)

procedure SafeCall(const aData: NSData);
begin
  aData.release;
end;

var
  aData: NSData;
begin
  try
    SafeCall(aData);
  except
  end;
end;

这篇关于为什么异常没有被try ...除了end;捕获?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆