delphi – 如何检查对程序的引用是否为零?

前端之家收集整理的这篇文章主要介绍了delphi – 如何检查对程序的引用是否为零?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
在以下示例代码中,对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,否则会导致访问冲突.

任何想法为什么会失败,如何解决

  1. program InvokeTest2;
  2.  
  3. {$APPTYPE CONSOLE}
  4.  
  5. uses
  6. System.SysUtils;
  7.  
  8. type
  9. TSafeCall<T> = class
  10. public
  11. type
  12. TNotifyProc = reference to procedure (Item: T);
  13. class procedure Invoke(NotifyProc: TNotifyProc; Item: T); overload;
  14. end;
  15.  
  16. TOnObj = procedure (Value: String) of object;
  17.  
  18. { TSafeCall<T> }
  19.  
  20. class procedure TSafeCall<T>.Invoke(NotifyProc: TNotifyProc; Item: T);
  21. begin
  22. if Assigned(NotifyProc) then
  23. NotifyProc(Item);
  24. end;
  25.  
  26. procedure AssertTestObj(OnExceptionObj_: TOnObj; Value_: String);
  27. begin
  28. TSafeCall<String>.Invoke(OnExceptionObj_,Value_);
  29. end;
  30.  
  31. begin
  32. try
  33. TSafeCall<String>.Invoke(nil,'works as expected');
  34.  
  35. AssertTestObj(nil,'this causes an access violation!');
  36. except
  37. on E: Exception do
  38. Writeln(E.ClassName,': ',E.Message);
  39. end;
  40. end.

解决方法

这是一个编译器错误.这是我简化的复制品:
  1. {$APPTYPE CONSOLE}
  2.  
  3. type
  4. TProc = reference to procedure;
  5. TOnObject = procedure of object;
  6.  
  7. procedure Invoke(Proc: TProc);
  8. begin
  9. if Assigned(Proc) then
  10. Proc();
  11. end;
  12.  
  13. procedure CallInvokeOnObject(OnObject: TOnObject);
  14. begin
  15. Invoke(OnObject);
  16. end;
  17.  
  18. begin
  19. Invoke(nil); // succeeds
  20. CallInvokeOnObject(nil); // results in AV
  21. end.

你可能会想我为什么简化.你的代码是一个很好的复制问题.不过,我想让它绝对尽可能简单,所以我真的可以确定问题是我认为的.所以我删除了泛型和类.

现在,使用Assigned的测试是正确的.你是对的,期望它会按照你的意图行事.问题是当编译器生成从CallInvokeOnObject调用Invoke的代码时,它需要在引用过程接口中包装对象的方法.为了正确地做到这一点,需要测试对象的方法是否被分配.如果没有,那么不应该创建包装器接口,并且Invoke应该被传递为零.

编译器无法做到这一点.它无条件地将对象的方法包装在引用过程接口中.您可以在为CallInvokeOnObject发出的代码中看到这一点.

  1. Project1.dpr.16: begin // this is the beginning of CallInvokeOnObject
  2. 004064D8 55 push ebp
  3. 004064D9 8BEC mov ebp,esp
  4. 004064DB 6A00 push $00
  5. 004064DD 53 push ebx
  6. 004064DE 33C0 xor eax,eax
  7. 004064E0 55 push ebp
  8. 004064E1 683B654000 push $0040653b
  9. 004064E6 64FF30 push dword ptr fs:[eax]
  10. 004064E9 648920 mov fs:[eax],esp
  11. 004064EC B201 mov dl,$01
  12. 004064EE A1F4634000 mov eax,[$004063f4]
  13. 004064F3 E8DCDAFFFF call TObject.Create
  14. 004064F8 8BD8 mov ebx,eax
  15. 004064FA 8D45FC lea eax,[ebp-$04]
  16. 004064FD 8BD3 mov edx,ebx
  17. 004064FF 85D2 test edx,edx
  18. 00406501 7403 jz $00406506
  19. 00406503 83EAF8 sub edx,-$08
  20. 00406506 E881F2FFFF call @IntfCopy
  21. 0040650B 8B4508 mov eax,[ebp+$08]
  22. 0040650E 894310 mov [ebx+$10],eax
  23. 00406511 8B450C mov eax,[ebp+$0c]
  24. 00406514 894314 mov [ebx+$14],eax
  25. Project18.dpr.17: Invoke(OnObject);
  26. 00406517 8BC3 mov eax,ebx
  27. 00406519 85C0 test eax,eax
  28. 0040651B 7403 jz $00406520
  29. 0040651D 83E8E8 sub eax,-$18
  30. 00406520 E8DFFDFFFF call Invoke

对TObject.Create的调用是在引用过程接口中包含对象的方法.请注意,该接口是无条件创建的,然后传递给Invoke.

没有办法在Invoke内部解决这个问题.到代码到达那里的时候太晚了.您无法检测到该方法未分配.这应该报告给Embarcadero作为一个错误.

您唯一可行的解​​决方法是在CallInvokeOnObject中添加额外的分配检查.

猜你在找的Delphi相关文章