TmT*_*ron 15 delphi delphi-xe3
在以下示例中,调用将AssertTestObj()导致访问冲突.
Project InvokeTest2.exe引发异常类$ C0000005,并显示消息'0x00000000访问冲突:读取地址0x00000000'.
在调试时我可以看到Assigned(NotifyProc)测试中TSafeCall<T>.Invoke()没有按预期工作 - 因此Invoke()尝试执行NotifyProc哪个nil因此导致访问冲突.
任何想法为什么失败以及如何解决它?
program InvokeTest2;
{$APPTYPE CONSOLE}
uses
System.SysUtils;
type
TSafeCall<T> = class
public
type
TNotifyProc = reference to procedure (Item: T);
class procedure Invoke(NotifyProc: TNotifyProc; Item: T); overload;
end;
TOnObj = procedure (Value: String) of object;
{ TSafeCall<T> }
class procedure TSafeCall<T>.Invoke(NotifyProc: TNotifyProc; Item: T);
begin
if Assigned(NotifyProc) then
NotifyProc(Item);
end;
procedure AssertTestObj(OnExceptionObj_: TOnObj; Value_: String);
begin
TSafeCall<String>.Invoke(OnExceptionObj_, Value_);
end;
begin
try
TSafeCall<String>.Invoke(nil, 'works as expected');
AssertTestObj(nil, 'this causes an access violation!');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Run Code Online (Sandbox Code Playgroud)
Dav*_*nan 19
这是编译器错误.这是我简化的复制品:
{$APPTYPE CONSOLE}
type
TProc = reference to procedure;
TOnObject = procedure of object;
procedure Invoke(Proc: TProc);
begin
if Assigned(Proc) then
Proc();
end;
procedure CallInvokeOnObject(OnObject: TOnObject);
begin
Invoke(OnObject);
end;
begin
Invoke(nil); // succeeds
CallInvokeOnObject(nil); // results in AV
end.
Run Code Online (Sandbox Code Playgroud)
你可能想知道为什么我简化了.您的代码是对问题的极好再现.但是,我想让它尽可能简单,这样我才能确定问题是我认为的那样.所以我删除了泛型和类.
现在,测试使用Assigned是正确的.你有理由期望它会按照你的意愿行事.的问题是,当编译器生成的代码来调用Invoke从CallInvokeOnObject,它需要包裹物体的方法在参考程序接口.为了正确地执行此操作,需要测试是否分配了对象的方法.如果没有,则不应创建包装器接口,Invoke应该传递nil.
编译器无法做到这一点.它无条件地将对象的方法包装在引用过程接口中.您可以在发出的代码中看到这一点CallInvokeOnObject.
Project1.dpr.16: begin // this is the beginning of CallInvokeOnObject 004064D8 55 push ebp 004064D9 8BEC mov ebp,esp 004064DB 6A00 push $00 004064DD 53 push ebx 004064DE 33C0 xor eax,eax 004064E0 55 push ebp 004064E1 683B654000 push $0040653b 004064E6 64FF30 push dword ptr fs:[eax] 004064E9 648920 mov fs:[eax],esp 004064EC B201 mov dl,$01 004064EE A1F4634000 mov eax,[$004063f4] 004064F3 E8DCDAFFFF call TObject.Create 004064F8 8BD8 mov ebx,eax 004064FA 8D45FC lea eax,[ebp-$04] 004064FD 8BD3 mov edx,ebx 004064FF 85D2 test edx,edx 00406501 7403 jz $00406506 00406503 83EAF8 sub edx,-$08 00406506 E881F2FFFF call @IntfCopy 0040650B 8B4508 mov eax,[ebp+$08] 0040650E 894310 mov [ebx+$10],eax 00406511 8B450C mov eax,[ebp+$0c] 00406514 894314 mov [ebx+$14],eax Project18.dpr.17: Invoke(OnObject); 00406517 8BC3 mov eax,ebx 00406519 85C0 test eax,eax 0040651B 7403 jz $00406520 0040651D 83E8E8 sub eax,-$18 00406520 E8DFFDFFFF call Invoke
该调用TObject.Create是在引用过程接口中包装对象的方法.请注意,接口是无条件创建的,然后传递给Invoke.
你无法从里面解决这个问题Invoke.当代码到达那里时已经太晚了.您无法检测到该方法未分配.这应该作为一个错误报告给Embarcadero.
您唯一可行的解决方法是添加额外的分配签入CallInvokeOnObject.