为什么异常没有被try ...除了end;捕获? [英] Why the exception is not caught by the try... except 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屋!