为什么异常没有被 try...except end; 捕获?
Why the exception is not caught by the try... except end;?
我有这段代码(在 iOS 和 Delphi 东京运行):
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;
正常情况下,异常应该在 except end
块中捕获,但在这种情况下,它不会被处理程序捕获,而是传播到 Application.OnException
处理程序。
Access violation at address 0000000100EE9A8C, accessing address
0000000000000000
我是不是漏掉了什么?
这是 bug(实际上是 feature)在 iOS 和 Android 平台(可能在具有 LLVM 后端的其他人上——尽管它们没有明确记录)。
核心问题是 nil
引用上的虚方法调用引起的异常构成了硬件异常,它没有被最近的异常处理程序捕获,并且传播到下一个异常处理程序(在本例中为应用程序异常处理程序).
Use a Function Call in a try-except Block to Prevent Uncaught Hardware Exceptions
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 平台上出现问题的最简单代码是:
var
aData: IInterface;
begin
try
aData._Release;
except
end;
end;
在 Windows 平台上执行上述代码按预期工作,异常处理程序捕获异常。上面的代码中没有 nil
赋值,因为 aData
是接口引用,它们在所有平台上都会被编译器自动置零。添加 nil
赋值是多余的,不会改变结果。
表明异常是由虚方法调用引起的
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;
在以下所有代码变体中,异常都会转义异常处理程序。
var
aData: IFoo;
begin
try
aData.Foo;
except
end;
end;
var
aData: TFoo;
begin
try
aData.Foo;
except
end;
end;
即使我们更改 Foo
方法实现并从中删除所有代码,它仍然会导致转义异常。
如果我们将 Foo
声明从 virtual 更改为 static,由于除法为零导致的异常将被正确捕获,因为允许调用 nil
引用上的静态方法并且调用本身不会抛出任何异常异常 - 因此构成文档中提到的函数调用。
type
TFoo = class(TInterfacedObject, IFoo)
public
procedure Foo;
end;
TFoo = class(TObject)
public
procedure Foo;
end;
另一种也会导致异常并得到正确处理的静态方法变体是将 x
声明为 TFoo
class 字段并在 Foo
方法中访问该字段。
TFoo = class(TObject)
public
x: Integer;
procedure Foo;
end;
procedure TFoo.Foo;
var
x: integer;
begin
x := 5;
end;
回到涉及 NSData
引用的原始问题。 NSData
是 Objective-C class 并且这些在 Delphi.
中表示为接口
// root interface declaration for all Objective-C classes and protocols
IObjectiveC = interface(IInterface)
[IID_IObjectiveC_Name]
end;
由于在接口引用上调用方法始终是通过 VMT table 的虚拟调用,在这种情况下,其行为方式与直接在对象引用上调用虚拟方法调用的方式相似(表现出相同的问题)。调用本身会引发异常,并且不会被最近的异常处理程序捕获。
解决方法:
其中引用可能是 nil
的代码中的一种变通方法是在对其调用虚拟方法之前检查它是否存在 nil
。如果需要,在 nil
引用的情况下,我们还可以引发常规异常,该异常将通过包含异常处理程序被正确捕获。
var
aData: NSData;
begin
try
if Assigned(aData) then
aData.release
else
raise Exception.Create('NSData is nil');
except
end;
end;
文档中提到的另一种解决方法是将代码放入附加函数(方法)
procedure SafeCall(const aData: NSData);
begin
aData.release;
end;
var
aData: NSData;
begin
try
SafeCall(aData);
except
end;
end;
我有这段代码(在 iOS 和 Delphi 东京运行):
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;
正常情况下,异常应该在 except end
块中捕获,但在这种情况下,它不会被处理程序捕获,而是传播到 Application.OnException
处理程序。
Access violation at address 0000000100EE9A8C, accessing address 0000000000000000
我是不是漏掉了什么?
这是 bug(实际上是 feature)在 iOS 和 Android 平台(可能在具有 LLVM 后端的其他人上——尽管它们没有明确记录)。
核心问题是 nil
引用上的虚方法调用引起的异常构成了硬件异常,它没有被最近的异常处理程序捕获,并且传播到下一个异常处理程序(在本例中为应用程序异常处理程序).
Use a Function Call in a try-except Block to Prevent Uncaught Hardware Exceptions
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 平台上出现问题的最简单代码是:
var
aData: IInterface;
begin
try
aData._Release;
except
end;
end;
在 Windows 平台上执行上述代码按预期工作,异常处理程序捕获异常。上面的代码中没有 nil
赋值,因为 aData
是接口引用,它们在所有平台上都会被编译器自动置零。添加 nil
赋值是多余的,不会改变结果。
表明异常是由虚方法调用引起的
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;
在以下所有代码变体中,异常都会转义异常处理程序。
var
aData: IFoo;
begin
try
aData.Foo;
except
end;
end;
var
aData: TFoo;
begin
try
aData.Foo;
except
end;
end;
即使我们更改 Foo
方法实现并从中删除所有代码,它仍然会导致转义异常。
如果我们将 Foo
声明从 virtual 更改为 static,由于除法为零导致的异常将被正确捕获,因为允许调用 nil
引用上的静态方法并且调用本身不会抛出任何异常异常 - 因此构成文档中提到的函数调用。
type
TFoo = class(TInterfacedObject, IFoo)
public
procedure Foo;
end;
TFoo = class(TObject)
public
procedure Foo;
end;
另一种也会导致异常并得到正确处理的静态方法变体是将 x
声明为 TFoo
class 字段并在 Foo
方法中访问该字段。
TFoo = class(TObject)
public
x: Integer;
procedure Foo;
end;
procedure TFoo.Foo;
var
x: integer;
begin
x := 5;
end;
回到涉及 NSData
引用的原始问题。 NSData
是 Objective-C class 并且这些在 Delphi.
// root interface declaration for all Objective-C classes and protocols
IObjectiveC = interface(IInterface)
[IID_IObjectiveC_Name]
end;
由于在接口引用上调用方法始终是通过 VMT table 的虚拟调用,在这种情况下,其行为方式与直接在对象引用上调用虚拟方法调用的方式相似(表现出相同的问题)。调用本身会引发异常,并且不会被最近的异常处理程序捕获。
解决方法:
其中引用可能是 nil
的代码中的一种变通方法是在对其调用虚拟方法之前检查它是否存在 nil
。如果需要,在 nil
引用的情况下,我们还可以引发常规异常,该异常将通过包含异常处理程序被正确捕获。
var
aData: NSData;
begin
try
if Assigned(aData) then
aData.release
else
raise Exception.Create('NSData is nil');
except
end;
end;
文档中提到的另一种解决方法是将代码放入附加函数(方法)
procedure SafeCall(const aData: NSData);
begin
aData.release;
end;
var
aData: NSData;
begin
try
SafeCall(aData);
except
end;
end;