lok*_*oki 15 delphi firemonkey delphi-10.2-tokyo
我有这个代码(在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;
Run Code Online (Sandbox Code Playgroud)
通常,异常应该在except end块中捕获,但在这种情况下,它不会被处理程序捕获并且会传播到Application.OnException处理程序.
地址0000000100EE9A8C的访问冲突,访问地址0000000000000000
我错过了什么?
Dal*_*kar 16
这是iOS和Android平台上的一个错误(实际上是一个功能)(可能是其他有LLVM后端的错误 - 尽管它们没有明确记录).
核心问题是由nil引用上的虚方法调用引起的异常构成了最近的异常处理程序未捕获的硬件异常,并且它被传播到下一个异常处理程序(在本例中为Application异常处理程序).
在try-except块中使用函数调用来防止未捕获的硬件异常
对于iOS设备的编译器,只有try块包含方法或函数调用时,块才能捕获硬件异常.这是与编译器的LLVM后端相关的差异,如果在try块中没有调用方法/函数,则无法返回.
在iOS和Android平台上展示问题的最简单的代码是:
var
aData: IInterface;
begin
try
aData._Release;
except
end;
end;
Run Code Online (Sandbox Code Playgroud)
在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;
Run Code Online (Sandbox Code Playgroud)
在以下所有代码变体中,异常转义异常处理程序.
var
aData: IFoo;
begin
try
aData.Foo;
except
end;
end;
var
aData: TFoo;
begin
try
aData.Foo;
except
end;
end;
Run Code Online (Sandbox Code Playgroud)
即使我们更改Foo方法实现并从中删除所有代码,它仍将导致转义异常.
如果我们将Foo声明从虚拟更改为静态,则将正确捕获由除以零引起的异常,因为nil允许对引用上的静态方法进行调用,并且调用本身不会抛出任何异常 - 因此构成文档中提到的函数调用.
type
TFoo = class(TInterfacedObject, IFoo)
public
procedure Foo;
end;
TFoo = class(TObject)
public
procedure Foo;
end;
Run Code Online (Sandbox Code Playgroud)
另一个静态方法变体也导致正确处理的异常声明x为TFoo类字段并在Foo方法中访问该字段.
TFoo = class(TObject)
public
x: Integer;
procedure Foo;
end;
procedure TFoo.Foo;
var
x: integer;
begin
x := 5;
end;
Run Code Online (Sandbox Code Playgroud)
回到涉及NSData参考的原始问题.NSData是Objective-C类,它们在Delphi中表示为接口.
// root interface declaration for all Objective-C classes and protocols
IObjectiveC = interface(IInterface)
[IID_IObjectiveC_Name]
end;
Run Code Online (Sandbox Code Playgroud)
由于在接口引用上调用方法始终是通过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;
Run Code Online (Sandbox Code Playgroud)
文档中提到的另一种解决方法是将代码放入附加功能(方法)
procedure SafeCall(const aData: NSData);
begin
aData.release;
end;
var
aData: NSData;
begin
try
SafeCall(aData);
except
end;
end;
Run Code Online (Sandbox Code Playgroud)