在以下示例代码中,对AssertTestObj()的调用会导致访问冲突.
Project InvokeTest2.exe raised exception class $C0000005 with message
‘access violation at 0x00000000: read of address 0x00000000’.
调试时,我可以看到TSafeCall< T> .Invoke()中的Assigned(NotifyProc)测试不能按预期的方式运行 – 所以Invoke()会尝试执行NotifyProc,否则会导致访问冲突.
任何想法为什么会失败,如何解决?
- 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.
解决方法
这是一个编译器错误.这是我简化的复制品:
- {$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.
你可能会想我为什么简化.你的代码是一个很好的复制问题.不过,我想让它绝对尽可能简单,所以我真的可以确定问题是我认为的.所以我删除了泛型和类.
现在,使用Assigned的测试是正确的.你是对的,期望它会按照你的意图行事.问题是当编译器生成从CallInvokeOnObject调用Invoke的代码时,它需要在引用过程接口中包装对象的方法.为了正确地做到这一点,需要测试对象的方法是否被分配.如果没有,那么不应该创建包装器接口,并且Invoke应该被传递为零.
编译器无法做到这一点.它无条件地将对象的方法包装在引用过程接口中.您可以在为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作为一个错误.