Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Unstimmigkeit bei Referenzzählung von Interfaces? (https://www.delphipraxis.net/195628-unstimmigkeit-bei-referenzzaehlung-von-interfaces.html)

Elrond 14. Mär 2018 13:08

Delphi-Version: 10 Berlin

Unstimmigkeit bei Referenzzählung von Interfaces?
 
Es sieht so aus als würde Delphi eine Referenz doppelt zählen wenn die Zielvariable global ist.
Vermutlich lässt es sich mit den folgenden Beispiel besser beschreiben:

Code:
program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils,
  Unit1 in 'Unit1.pas';

var
  IntfA: InterfaceA;
  TempA : Temp;

begin
  try
  TempA := Temp.Create;
  TempA.LocalTest;
    IntfA := TempA.GetInterfaceA; //_AddRef wird einmal für das Result von GetInterfaceA aufgerufen und einmal für die Zuweisung zu IntfA
    IntfA := nil; //_Release wird erwartungsgemäß aufgerufen, aber erst beim beenden wird ein weiteres mal _Release aufgerufen, wer hält also die zweite Referenz? 
    Writeln('?');
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;

end.
Code:
unit Unit1;

interface

type

  InterfaceA = interface(IInterface)
    ['{A1229A9D-E207-4CBD-9432-FEFD9D3C1684}']
    function Add(sum1, sum2: Integer): Integer;

  end;

  ImplA = class(TInterfacedObject, InterfaceA)
  public
    function Add(sum1, sum2: Integer): Integer;

  end;

  Temp = class
  public
    function GetInterfaceA: InterfaceA;
    procedure LocalTest;
  end;

implementation

{ Temp }

function Temp.GetInterfaceA: InterfaceA;
begin
  Result := ImplA.Create;
end;

procedure Temp.LocalTest;
var
  IntfA: InterfaceA;
begin
  IntfA := Self.GetInterfaceA; //_AddRef wird hier jedoch nur ein einziges mal aufgerufen, woher kommt der Unterschied? 
  IntfA := nil;  //_Release wird erwartungsgemäß aufgerufen
end;

{ ImplA }

function ImplA.Add(sum1, sum2: Integer): Integer;
begin
  Result := 42;
end;

end.

Stevie 14. Mär 2018 13:25

AW: Unstimmigkeit bei Referenzzählung von Interfaces?
 
Bei der Rückgabe eines Interfaces aus einer Funktion macht Delphi folgendes - Pseudocode:

Delphi-Quellcode:
var
  meineVariable: IInterface;
  compilerTempVariable: IInterface;
begin
  compilerTempVariable := MeineFunktion;
  meineVariable := compilerTempVariable;
  ...
end
Die vom Compiler implizit erzeugte Variable wird erst am Ende des jeweiligen Scopes finalisiert.

Hier ein Auszug aus dem Disassembly deiner Anwendung:

Code:
Project1.dpr.17: IntfA := TempA.GetInterfaceA; //_AddRef wird einmal für das Result von GetInterfaceA aufgerufen und einmal für die Zuweisung zu IntfA
0041D596 8D55EC          lea edx,[ebp-$14] // <- temp Variable
0041D599 A1BC584200       mov eax,[$004258bc]
0041D59E E8C5D8FFFF      call Temp.GetInterfaceA
0041D5A3 8B55EC          mov edx,[ebp-$14]
0041D5A6 B8B8584200       mov eax,$004258b8
0041D5AB E870CEFEFF      call @IntfCopy // <- zuweisen von temp Variable auf IntfA
Packen wir den gesamten Code in eine Routine, sieht der Code an dieser Stelle wie folgt aus (wie z.B. in deiner LocalTest Methode):

Code:
Project1.dpr.18: IntfA := TempA.GetInterfaceA; //_AddRef wird einmal für das Result von GetInterfaceA aufgerufen und einmal für die Zuweisung zu IntfA
0041AF4C 8D55FC          lea edx,[ebp-$04] // <- IntfA
0041AF4F 8B45F8           mov eax,[ebp-$08] // <- TempA
0041AF52 E811FFFFFF      call Temp.GetInterfaceA
Warum ist das so? Dazu muss man folgendes wissen: Bei einem Result vom Typ Interface (und anderen gemanageten Typen auch) wird der Methode das Result als var Parameter übergeben.
Das kann man z.B. sehen, wenn du einfach mal zweimal hintereinander
Delphi-Quellcode:
IntfA := TempA.GetInterfaceA;
aufrufst und einen Stoppunkt in GetInterfaceA setzt, dann siehst du das beim zweiten Aufruf in Result schon was drin steht, nämlich der Wert aus deim ersten Aufruf.

Und weil das so ist, sichert der Compiler diesen Code gegen mögliche Exceptions ab. Wenn nämlich nach der Zuweisung auf Result eine Exception in GetInterfaceA auftreten würde, wäre trotzdem die Result zuweisung schon passiert, was aber nicht sein darf, weil die Funktion ja nicht ordnungsgemäß zurück kommt und demnach die Zuweisung von
Delphi-Quellcode:
IntfA := TempA.GetInterfaceA;
nicht passieren wird. Handelt es sich hier aber um eine lokale Variable, kann der Compiler auf diese Absicherung über die temporäre Variable verzichten, da niemand anders außerhalb dieser Routine auf diesen Wert zugreifen kann - was bei einer globalen Variable anders sein kann.

Was der Compiler im Übrigen nicht absichert, ist folgendes:

Delphi-Quellcode:
try
  IntfA := TempA.GetInterfaceA;
except
end;
IntfA := TempA.GetInterfaceA;
Angenommen beim ersten Aufruf von GetInterfaceA kommt nach dem Result zuweisen noch eine Exception. IntfA ist nun trotzdem nicht mehr nil, weil das passiert, was ich oben beschrieben habe.

Uwe Raabe 14. Mär 2018 13:34

AW: Unstimmigkeit bei Referenzzählung von Interfaces?
 
Der Effekt wird auch hier bei StackOverflow beschrieben: The mysterious case of the unexpected implicit interface variable

Elrond 14. Mär 2018 13:49

AW: Unstimmigkeit bei Referenzzählung von Interfaces?
 
@Stevie

Danke für die ausführliche Erklärung!
Auch wenn ich Delphi nicht mag, die Community ist spitze :thumb:


Alle Zeitangaben in WEZ +1. Es ist jetzt 04:31 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz