2015-03-16 1 views
15

Im folgenden Beispielcode verursacht der Aufruf an AssertTestObj() eine Zugriffsverletzung.Wie überprüft man, ob ein Verweis auf die Prozedur null ist?

Projekt InvokeTest2.exe angehoben Ausnahmeklasse $ C0000005 mit Meldung 'Zugriffsverletzung bei 0x00000000: Lesen der Adresse 0x00000000'.

beim Debuggen ich sehen kann, dass der Assigned(NotifyProc) Test in TSafeCall<T>.Invoke() nicht wie erwartet - so dass Invoke() versucht NotifyProc auszuführen, das nil ist und somit bewirkt, dass die Zugriffsverletzung.

Irgendwelche Ideen, warum das scheitert und wie man es löst?

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. 

Antwort

18

Dies ist ein Compilerfehler. Hier ist meine vereinfachte Wiedergabe:

{$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. 

Sie könnten sich fragen, warum ich vereinfacht. Ihr Code war eine hervorragende Reproduktion des Problems. Ich wollte es jedoch so einfach wie möglich machen, damit ich wirklich sicher sein konnte, dass das Problem das war, was ich glaube. Also habe ich die Generika und die Klassen entfernt.

Jetzt ist der Test mit Assigned korrekt. Sie haben zu Recht erwartet, dass es sich so verhält, wie Sie es beabsichtigen. Das Problem besteht darin, dass wenn der Compiler Code generiert, um Invoke von CallInvokeOnObject aufzurufen, die Methode des Objekts in einer Referenzprozedurschnittstelle umbrechen muss. Um dies richtig durchzuführen, müsste geprüft werden, ob die Objektmethode zugewiesen ist oder nicht. Wenn nicht, sollte keine Wrapper-Schnittstelle erstellt werden und Invoke sollte nil übergeben werden.

Der Compiler schlägt das nicht. Sie umschließt die Objektmethode in einer Referenzprozedurschnittstelle bedingungslos. Sie können dies in dem Code sehen, der für CallInvokeOnObject ausgegeben wird.

 
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 

das zu TObject.Create nennen ist, was die Methode des Objektes in einem Referenzverfahren Schnittstelle einwickelt. Beachten Sie, dass die Schnittstelle bedingungslos erstellt und dann an Invoke übergeben wird.

Es gibt keine Möglichkeit für Sie, dies von innen zu umgehen Invoke. Wenn der Code dort ankommt, ist es zu spät. Sie können nicht feststellen, dass die Methode nicht zugewiesen ist. Dies sollte Embarcadero als Fehler gemeldet werden.

Ihre einzige praktikable Problemumgehung ist, eine zusätzliche zugewiesene Überprüfung in CallInvokeOnObject hinzuzufügen.

+2

gemeldet als [RSP-10204] (https://quality.embarcadero.com/browse/RSP-10204) –

+3

@Martin Sie könnten die Version sinnvollerweise auf XE7 Update 1 mit der Versionsnummer 21.0.17707.5020 ändern. Das ist das Neueste, und wo ich meine Tests gemacht habe. –

Verwandte Themen