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,否則會導致訪問衝突.
任何想法為什麼會失敗,如何解決?
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 55push ebp 004064D9 8BECmov ebp,esp 004064DB 6A00push $00 004064DD 53push ebx 004064DE 33C0xor eax,eax 004064E0 55push ebp 004064E1 683B654000push $0040653b 004064E6 64FF30push dword ptr fs:[eax] 004064E9 648920mov fs:[eax],esp 004064EC B201mov dl,$01 004064EE A1F4634000mov eax,[$004063f4] 004064F3 E8DCDAFFFFcall TObject.Create 004064F8 8BD8mov ebx,eax 004064FA 8D45FClea eax,[ebp-$04] 004064FD 8BD3mov edx,ebx 004064FF 85D2test edx,edx 00406501 7403jz $00406506 00406503 83EAF8sub edx,-$08 00406506 E881F2FFFFcall @IntfCopy 0040650B 8B4508mov eax,[ebp+$08] 0040650E 894310mov [ebx+$10],eax 00406511 8B450Cmov eax,[ebp+$0c] 00406514 894314mov [ebx+$14],eax Project18.dpr.17: Invoke(OnObject); 00406517 8BC3mov eax,ebx 00406519 85C0test eax,eax 0040651B 7403jz $00406520 0040651D 83E8E8sub eax,-$18 00406520 E8DFFDFFFFcall Invoke
對TObject.Create的呼叫是在引用過程介面中包含物件的方法.請注意,該介面是無條件建立的,然後傳遞給Invoke.
沒有辦法在Invoke內部解決這個問題.到程式碼到達那裡的時候太晚了.您無法檢測到該方法未分配.這應該報告給Embarcadero作為一個錯誤.
您唯一可行的解決方法是在CallInvokeOnObject中新增額外的分配檢查.
http://stackoverflow.com/questions/29073867/how-to-check-if-a-reference-to-procedure-is-nil