Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Memory Leak bei Interface Delegation (https://www.delphipraxis.net/190686-memory-leak-bei-interface-delegation.html)

dpg123 27. Okt 2016 10:02

Delphi-Version: 2006

Memory Leak bei Interface Delegation
 
Hello zusammen,

arbeite mich gerade in das Thema Interfaces, deren Delegation und Reference Counting ein. Beim Experimentieren bin ich auf folgendes Minimalbsp gestoßen, bei dem mir FastMM ein Memory Leak anzeigt und ich habe keinen Schimmer warum und wie ich es weg bekomme

Delphi-Quellcode:

type

  IMyInterface = interface

  end;

  TMyClass = class(TInterfacedObject, IMyInterface)

  end;

  TMyWrapper = class(TInterfacedObject, IMyInterface)

    FMyInterface: IMyInterface;

    property MyInterface: IMyInterface read FMyInterface implements IMyInterface;

  end;
Aufruf:

Delphi-Quellcode:
procedure Test;

var
  MyInterface: IMyInterface;

begin

  MyInterface := TMyWrapper.Create(); // <- mit dieser Zeile Memory Leak

//  MyInterface := TMyClass .Create(); // <- mit dieser Zeile KEIN Memory Leak

end;
Man beachte, dass ich mit FMyInterface in TMyWrapper noch nichts gemacht habe! Alleine durch das Deklarieren der Property geht anscheinend iwas beim Reference Counting schief...

FastMM meldet:

Zitat:

---------------------------
Test.exe: Memory Leak Detected
---------------------------
This application has leaked memory. The small block leaks are (excluding expected leaks registered by pointer):

5 - 12 bytes: TMyWrapper x 1

Was mache ich falsch? Mit FMyInterface passiert nichts, es gibt keinen Zugriff auf die property. Wo ist der Unterschied zur TMyClass-Zeile, bei der das Reference Counting funktioniert? Wie delegiere ich richtig?

Dank und Gruß!

himitsu 27. Okt 2016 10:10

AW: Memory Leak bei Interface Delegation
 
[edit] falsch geguckt.

Hier auch?
Delphi-Quellcode:
procedure Test;

procedure TestProc;
var
  MyInterface: IMyInterface;
begin
  MyInterface := TMyWrapper.Create(); // <- mit dieser Zeile Memory Leak

  //MyInterface := TMyClass .Create(); // <- mit dieser Zeile KEIN Memory Leak
end;

begin
  TestProc;
end;
Und was ist hier?
Zitat:

procedure Test;
var
MyInterface: IMyInterface;
begin
MyInterface := TMyWrapper.Create(); // <- mit dieser Zeile Memory Leak
// MyInterface := TMyClass .Create(); // <- mit dieser Zeile KEIN Memory Leak

MyInterface := nil;
end;
Am Besten niemals mit globalen Variablen und nicht in der Mainfunction testen.

dpg123 27. Okt 2016 10:17

AW: Memory Leak bei Interface Delegation
 
Hat leider beides nicht geholfen...

globale Variablen hab ich nicht... alles lokal im FormCreate-Event.

himitsu 27. Okt 2016 10:44

AW: Memory Leak bei Interface Delegation
 
Joar, hatte irgendwie das procedure als project gelesen. :oops:

Aaaaaaaaalso, im XE das Selbe.


Es liegt am Implements.
Aus irgendeinem Grund besitzt TMyWrapper zwei Referenzen, anstatt nur Einer.

Und da es nur eine Variable und auch keine "versteckte" Tempvariable gibt, steht RefCount am Ende (nach __Release im
Delphi-Quellcode:
end;
) immernoch über 0, womit die Isntanz natürlich nicht freigegeben wird.

So
Delphi-Quellcode:
property MyInterface: IMyInterface read FMyInterface ;//implements IMyInterface;
gibt es kein Leck.

PS: Du hast vergessen der innere Interface zu erstellen.
So knallt das wunderschön, wenn man auf irgendwas von IInterface oder IMyInterface zugreifen will, da diese Referenz NIL ist.


Delphi-Quellcode:
type
  IMyInterface = interface
    procedure ShowRefCount;
  end;

  TMyClass = class(TInterfacedObject, IMyInterface)
    procedure ShowRefCount;
  end;

  TMyWrapper = class(TInterfacedObject, IMyInterface)
    FMyInterface: IMyInterface;
    constructor Create;
    property MyInterface: IMyInterface read FMyInterface implements IMyInterface;
  end;

procedure TMyClass.ShowRefCount;
begin
  ShowMessage(IntToStr(RefCount));
end;

constructor TMyWrapper.Create;
begin
  inherited;
  FMyInterface := TMyClass.Create;
end;

procedure TForm4.FormCreate(Sender: TObject);
var
  MyInterface: IMyInterface;
begin
  ReportMemoryLeaksOnShutdown := True;

  MyInterface := TMyWrapper.Create(); // <- mit dieser Zeile Memory Leak
  //MyInterface := TMyClass.Create(); // <- mit dieser Zeile KEIN Memory Leak

  //ShowMessage(IntToStr((MyInterface as TInterfacedObject).RefCount));
  MyInterface.ShowRefCount;
end;

Uwe Raabe 27. Okt 2016 11:21

AW: Memory Leak bei Interface Delegation
 
Zitat:

Zitat von himitsu (Beitrag 1352111)
Es liegt am Implements.
Aus irgendeinem Grund besitzt TMyWrapper zwei Referenzen, anstatt nur Einer.

Das liegt daran, daß eine Bedingung für die Aggregation verletzt wird:
Zitat:

Die zweite IInterface-Implementierung delegiert Aufrufe für QueryInterface, _AddRef, und _Release an das äußere Objekt. Die äußere IInterface-Implementierung wird als Controlling Unknown bezeichnet.

Fritzew 27. Okt 2016 11:50

AW: Memory Leak bei Interface Delegation
 
Hier noch ein paar Infos:

AggregatedObject


Stackoverflow


Delphi-Quellcode:
unit Unit1;

interface

type
  IMyInterface = interface
    procedure ShowInfo;
  end;

  // Ableiten von TAggregatedObject
  TMyClass = class(TAggregatedObject, IMyInterface)
    procedure ShowInfo;
  end;


  TMyWrapper = class(TInterfacedObject, IMyInterface)
   private
     // Wir "besitzen" also die Klasse selber speichern

     FMyClass: TMyClass;
    function getMyInterface: IMyInterface;

   public
    constructor Create;
    destructor Destroy; override;
    property MyInterface: IMyInterface read getMyInterface implements IMyInterface;
  end;

  procedure Test;
implementation

uses
  System.SysUtils;




procedure TMyClass.ShowInfo;
begin
  writeln('Call ShowInfo');
end;

constructor TMyWrapper.Create;
begin
  inherited;
  FMyClass := TMyClass.Create(self);
end;

destructor TMyWrapper.Destroy;
begin
  inherited;
  // Klasse freigeben
  FMyClass.Free;
end;

function TMyWrapper.getMyInterface: IMyInterface;
begin
  result := FMyClass as IMyInterface;
end;

procedure Test;
  var MyInterface: IMyInterface;
begin
  MyInterface := TMyWrapper.Create(); // <- mit dieser Zeile Memory Leak
  MyInterface.ShowInfo;

// Kein Leak :-)



end;


end.

himitsu 27. Okt 2016 12:16

AW: Memory Leak bei Interface Delegation
 
[F1] auf Delphi-Referenz durchsuchenimplements nennt zuerst die Variante mit dem Interface als Property und danach eine mit der Klasse.

Woebei die Klasse da nichtmal ein Interface ist, laut dem gezeigten Beispiel.
http://docwiki.embarcadero.com/RADSt..._Typ_Interface
Die OH verwendet auch nirgendwo einen Getter, der auf dem Object ein Interface macht, beim Zugrif.

Und nach meinem Verständnis sollte man eigentlich eh niemals Interface-Referenzen mit Objekt-Referenzen auf das selbe Objekt mischen?
(Ausnahme die TComponents, welche nicht referenzgezählt sind, auch wenn das schon ein bissl krank ist, denn das knallt, wenn man ein Free macht, bevor die letzte Interfacereferenz freigegeben wurde)

Beides vom Typ Interface und ich hab diesmal zwei Speicherlecks. (TInterfacedObject statt TAggregatedObject ergab nur ein Speicherleck :stupid:)
Delphi-Quellcode:
type
  IMyInterface = interface
    procedure Show;
  end;

  TMyClass = class(TAggregatedObject, IMyInterface)
    procedure Show;
  end;

  TMyWrapper = class(TInterfacedObject, IMyInterface)
  private
    FMyInterface: IMyInterface;
  public
    constructor Create;
    property MyInterface: IMyInterface read FMyInterface implements IMyInterface;
  end;

procedure TMyClass.Show;
begin
  ShowMessage('blubb');
end;

constructor TMyWrapper.Create;
begin
  inherited;
  FMyInterface := TMyClass.Create(Self);
end;

procedure TForm4.FormCreate(Sender: TObject);
var
  MyInterface: IMyInterface;
begin
  ReportMemoryLeaksOnShutdown := True;
  MyInterface := TMyWrapper.Create;
  MyInterface.Show;
end;
Mit der Klasse als interface hab ich kein Speicherleck. :cyclops:
Delphi-Quellcode:
type
  IMyInterface = interface
    procedure Show;
  end;

  TMyClass = class(TAggregatedObject, IMyInterface)
    procedure Show;
  end;

  TMyWrapper = class(TInterfacedObject, IMyInterface)
  private
    FMyClass: TMyClass;
  public
    constructor Create;
    destructor Destroy; override;
    property MyInterface: TMyClass read FMyClass implements IMyInterface;
  end;

procedure TMyClass.Show;
begin
  ShowMessage('blubb');
end;

constructor TMyWrapper.Create;
begin
  inherited;
  FMyClass := TMyClass.Create(Self);
end;

destructor TMyWrapper.Destroy;
begin
  FMyClass.Free;
  inherited;
end;

procedure TForm4.FormCreate(Sender: TObject);
var
  MyInterface: IMyInterface;
begin
  ReportMemoryLeaksOnShutdown := True;
  MyInterface := TMyWrapper.Create;
  MyInterface.Show;
end;
Die Klasse als Klasse funktioniert auch.
Delphi-Quellcode:
type
  IMyInterface = interface
    procedure Show;
  end;

  TMyClass = class
    procedure Show;
  end;

  TMyWrapper = class(TInterfacedObject, IMyInterface)
  private
    FMyClass: TMyClass;
  public
    constructor Create;
    destructor Destroy; override;
    property MyInterface: TMyClass read FMyClass implements IMyInterface;
  end;

procedure TMyClass.Show;
begin
  ShowMessage('blubb');
end;

constructor TMyWrapper.Create;
begin
  inherited;
  FMyClass := TMyClass.Create;
end;

destructor TMyWrapper.Destroy;
begin
  FMyClass.Free;
  inherited;
end;

procedure TForm4.FormCreate(Sender: TObject);
var
  MyInterface: IMyInterface;
begin
  ReportMemoryLeaksOnShutdown := True;
  MyInterface := TMyWrapper.Create;
  MyInterface.Show;
end;

jaenicke 27. Okt 2016 12:55

AW: Memory Leak bei Interface Delegation
 
Zitat:

Zitat von himitsu (Beitrag 1352115)
(Ausnahme die TComponents, welche nicht referenzgezählt sind, auch wenn das schon ein bissl krank ist, denn das knallt, wenn man ein Free macht, bevor die letzte Interfacereferenz freigegeben wurde)

Deshalb sollte man auch möglichst keine Interfaces auf TComponent-Nachkommen selbst nutzen. Wir haben z.B. ein GUI-Interface, das durch eine separate Klasse implementiert wird, die die visuelle Komponente lediglich kennt. Wird diese freigegeben, entfernt sie die Referenz, aber das separate Objekt bleibt bestehen. (Und leitet Zugriffe schlicht nicht mehr weiter.)

Uwe Raabe 28. Okt 2016 00:06

AW: Memory Leak bei Interface Delegation
 
Zitat:

Zitat von himitsu (Beitrag 1352115)
Woebei die Klasse da nichtmal ein Interface ist, laut dem gezeigten Beispiel.

Weil die Klasse das Interface zwar implementieren, aber es nicht auch noch unterstützen muss.

Das
Delphi-Quellcode:
implements
ist sogar noch flexibler:
Die Klasse, die bei dem
Delphi-Quellcode:
implements
steht, muss gar nicht das gesamte Interface implementieren. Es genügt, wenn die fehlenden Methoden von der Wrapper-Klasse implementiert werden.

Delphi-Quellcode:
type
  IMyInterface = interface
    procedure Foo;
    procedure Bar;
  end;

  TMyClass = class
  protected
    procedure Foo;
  end;

  TMySuperClass = class(TInterfacedObject, IMyInterface)
  private
    FMyClass: TMyClass;
    function GetMyClass: TMyClass;
  protected
    procedure Bar;
    property MyClass: TMyClass read GetMyClass implements IMyInterface;
  public
    destructor Destroy; override;
  end;
Das geht sogar noch weiter: Sollten beide Klassen eine Methode des Interfaces implementieren, dann hat die Implementation der Wrapper-Klasse Vorrang.

Delphi-Quellcode:
type
  IMyInterface = interface
    procedure Foo;
    procedure Bar;
  end;

  TMyClass = class
  protected
    procedure Foo;
    procedure Bar;
  end;

  TMySuperClass = class(TInterfacedObject, IMyInterface)
  private
    FMyClass: TMyClass;
    function GetMyClass: TMyClass;
  protected
    procedure Bar; // hat Vorrang for MyClass.Bar
    property MyClass: TMyClass read GetMyClass implements IMyInterface;
  public
    destructor Destroy; override;
  end;

Fritzew 28. Okt 2016 09:20

AW: Memory Leak bei Interface Delegation
 
Zitat:

Das geht sogar noch weiter: Sollten beide Klassen eine Methode des Interfaces implementieren, dann hat die Implementation der Wrapper-Klasse Vorrang.

Delphi-Quellcode:
type
  IMyInterface = interface
    procedure Foo;
    procedure Bar;
  end;

  TMyClass = class
  protected
    procedure Foo;
    procedure Bar;
  end;

  TMySuperClass = class(TInterfacedObject, IMyInterface)
  private
    FMyClass: TMyClass;
    function GetMyClass: TMyClass;
  protected
    procedure Bar; // hat Vorrang for MyClass.Bar
    property MyClass: TMyClass read GetMyClass implements IMyInterface;
  public
    destructor Destroy; override;
  end;

Ja das ist schon interessant, aber in meinen Augen auch nicht sehr übersichtlich. Versuch das mal einem nicht Delphianer beizubringen.
Wenn das property auch als interface deklariert wird gewinnt das property...
Muss man wissen oder leidvoll erfahren.

himitsu 28. Okt 2016 10:50

AW: Memory Leak bei Interface Delegation
 
Und wer gewinnst, wenn man mehrere Klassen delegiert?
Bestimmt der mit dein meisten Bytes im Code, wo mindstens ein Byte den Wert 666 hat. :stupid:

dpg123 4. Nov 2016 15:44

AW: Memory Leak bei Interface Delegation
 
Entschuldigt die späte Rückmeldung!

Ich muss noch mal nachfragen: sehe ich es richtig, dass es mit folgender Deklaration

Zitat:

Zitat von himitsu (Beitrag 1352115)
Delphi-Quellcode:
type
  IMyInterface = interface
    procedure Show;
  end;

  TMyClass = class(TAggregatedObject, IMyInterface)
    procedure Show;
  end;

  TMyWrapper = class(TInterfacedObject, IMyInterface)
  private
    FMyInterface: IMyInterface;
  public
    constructor Create;
    property MyInterface: IMyInterface read FMyInterface implements IMyInterface;
  end;

keine sauber Implementation ohne manuelles Freigeben von FMyInterface gibt? Und wie sähe diese mit Freigeben aus?

Dank und Gruß!

Der schöne Günther 25. Jul 2019 19:51

AW: Memory Leak bei Interface Delegation
 
Ich bin grade selber hierrüber gestoplert. Man braucht echt eine Weile bis man die Ursache gefunden hat. Soweit ich es sehe gibt es tatsächlich keine wirkliche Lösung für das Speicherleck. Bei der Zuweisung in die Variable zeigt er direkt auf das innere Objekt. Das implements-Schlüsselwort sollte einen zwingen an TContainedObject oder TAggregatedObject zu delegieren. Oder zumindest eine Warnung ausspucken.

Fazit: Ich streiche
Delphi-Quellcode:
implements
aus meinem Vokabular. Die paar Zeilen Tipparbeit für explizite Delegation an eine Instanz sind sowieso übersichtlicher.

freimatz 26. Jul 2019 15:01

AW: Memory Leak bei Interface Delegation
 
Zitat:

Zitat von himitsu (Beitrag 1352161)
Garbage Collector ... Delphianer erzeugen keinen Müll, also brauchen sie auch keinen Müllsucher.

Das passt ja genau hierher :duck:


Alle Zeitangaben in WEZ +1. Es ist jetzt 20:58 Uhr.

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