我有以下代码(在iOS上的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;
通常,异常应该在except end
块中捕获,但是在这种情况下,异常不会被处理程序捕获,并且会传播到Application.OnException
处理程序中。
访问冲突,地址为0000000100EE9A8C,正在访问地址 0000000000000000
我错过了什么吗?
答案 0 :(得分:16)
这是在iOS和Android平台上(可能是在其他具有LLVM后端的-尽管未明确记录)的 bug (实际上是功能)。
核心问题是,由nil
引用上的虚拟方法调用引起的异常构成了硬件异常,该异常未被最近的异常处理程序捕获,并且被传播到下一个异常处理程序(在本例中为应用程序异常处理程序)。
Use a Function Call in a try-except Block to Prevent Uncaught Hardware Exceptions
使用适用于iOS设备的编译器,除了块可以捕获硬件外 仅当try块包含方法或函数调用时才异常。 这与编译器的LLVM后端有关, 如果在try块中未调用任何方法/函数,则无法返回。
在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
声明从虚拟更改为静态,则将正确捕获除数为零的异常,因为允许在nil
引用上调用静态方法,并且调用本身不会引发任何异常-因此构成文档中提到的函数调用。
type
TFoo = class(TInterfacedObject, IFoo)
public
procedure Foo;
end;
TFoo = class(TObject)
public
procedure Foo;
end;
另一个也会导致异常得到正确处理的静态方法变体是将x
声明为TFoo
类字段,并在Foo
方法中访问该字段。
TFoo = class(TObject)
public
x: Integer;
procedure Foo;
end;
procedure TFoo.Foo;
var
x: integer;
begin
x := 5;
end;
返回涉及NSData
参考的原始问题。 NSData
是Objective-C类,它们在Delphi中表示为接口。
// root interface declaration for all Objective-C classes and protocols
IObjectiveC = interface(IInterface)
[IID_IObjectiveC_Name]
end;
由于接口引用上的调用方法始终是通过VMT表进行的虚拟调用,因此在这种情况下,其行为与直接在对象引用上调用的虚拟方法调用类似(表现出相同的问题)。该调用本身会引发异常,并且不会被最近的异常处理程序捕获。
解决方法:
其中引用可能为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;