AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

hängende Interfaces

Ein Thema von stahli · begonnen am 29. Jan 2016 · letzter Beitrag vom 3. Mär 2017
Antwort Antwort
Benutzerbild von stahli
stahli

Registriert seit: 26. Nov 2003
Ort: Halle/Saale
4.336 Beiträge
 
Delphi 11 Alexandria
 
#1

hängende Interfaces

  Alt 29. Jan 2016, 14:33
Ich kann das Problem erst mal nur allgemein beschreiben.
Vielleicht kann ja dennoch jemand etwas dazu sagen...

Ich habe eine Factory, die mir diverse Objekte erzeugt und als Interfaces heraus gibt.
Jede Klasse unterstützt idR mehrere Interfaces und die Unterstützung prüfe ich mit Supports().

Jetzt benötige ich aber diverse gegenseitige Referenzierungen und Eintragungen in diversen Listen.

EurekaLog zeigt mir entsprechend auch MemoryLeaks an, da die Objekte NATÜRLICH nicht aufgelöst werden.

ABER wiederum zeigt mit EurekaLog nur einzelne Objekte an, nicht aber die 1000 erzeugten Objekte von TMyClass, obwohl deren Destructoren aber definitiv nie durchlaufen wurden.


Ich habe daher drei grundsätzliche Fragen:

- Gibt es so von der Ferne eine Erklärung, warum EurekaLog meine 1000 Objekte nicht bemeckert, obwohl die definitiv nicht aufgelöst wurden?

- Gibt es eine Möglichkeit die "hängenden Referenzen" herauszufinden? Mir würde nur einfallen, das Projekt zu entkernen und Stück für Stück neu zusammen zu setzen und zu profilen.

- Inzwischen denke ich, dass die automatische Referenzzählung für mich doch nicht so sinnvoll ist. Interfaces möchte ich zwar wegen der austauschbaren Funktionalitäten und einheitlicher öffentlicher Schnittstellen weiter nutzen, aber die Lebenszeit und gegenseitige Referenzen würde ich doch lieber wieder selbst verwalten.

Den Themenkomplex hatten wir schon mal behandelt.
http://www.delphipraxis.net/166899-i...eferenzen.html
http://www.delphipraxis.net/159095-r...objekte-2.html

Ich werde jetzt mal in Richtung von Bummis Vorschlag überlegen: http://www.delphipraxis.net/1134394-post46.html.
Dann erzeugt die Factory auf Anforderung bestimmte Objekte und verwaltet auch deren Lebenszeit und die Freigabe.
Referenzen werden dann über eine ID zugewiesen. Wenn benötigt wird ein entsprechendes Interface anhand der ID abgerufen und nach Verwendung beim rauslaufen aus dem Scope wieder Counter-Reduziert.
Wenn das Abrufen über ein Dictionary oder binäre Liste läuft wäre das ja auch performant.

Dann gäbe es halt nicht so etwas wie
Delphi-Quellcode:
TPerson.NameDesVaters: string
begin
  Result := Vater.Name;
end;
sondern
Delphi-Quellcode:
TPerson.NameDesVaters: string
var
  Vater: IPerson;
begin
  // TPerson kennt nur eine "VaterId"
  Vater := TFactory.GetPerson(VaterId); // Wird aus dem Dict geholt bzw. ggf. zuvor wenn nötig instanziiert
  Result := Vater.Name;
end;
// Referenz auf Vater ist wieder aufgelöst

Die Interface-Objekte werden freigegeben, wenn die Factory diese aus ihrer Sammlung entfernt.

Wenn ich das so umsetzen kann sind die ersten beiden Fragen zwar im Grunde hinfällig, aber dennoch würden mich Antworten interessieren.
Stahli
http://www.StahliSoft.de
---
"Jetzt muss ich seh´n, dass ich kein Denkfehler mach...!?" Dittsche (2004)

Geändert von stahli (29. Jan 2016 um 14:38 Uhr)
  Mit Zitat antworten Zitat
mjustin

Registriert seit: 14. Apr 2008
3.004 Beiträge
 
Delphi 2009 Professional
 
#2

AW: hängende Interfaces

  Alt 29. Jan 2016, 15:23
Bei gegenseitigen Referenzierungen verwende ich DUnit Tests, in denen Varianten der Erzeugung und Freigabe ausgeführt werden. Mit ReportMemoryLeakDetailsOnShutDown kontrolliert der DUnit TestRunner beim Beenden, ob es noch nicht freigegebene Instanzen gibt.

Da ich bisher alle Referenzprobleme lösen konnte (die Bibliotheken laufen auch unter Free Pascal mit TInterfacedObject ohne Memory Leaks), verwende ich die Referenzzählung mittlerweile ohne Bauchschmerzen. In hartnäckigen Fällen hilft es, die Anzahl der Referenzen auszugeben, die vor und nach Operationen mit einerm Interface existieren.
Michael Justin
  Mit Zitat antworten Zitat
Benutzerbild von Stevie
Stevie

Registriert seit: 12. Aug 2003
Ort: Soest
4.008 Beiträge
 
Delphi 10.1 Berlin Enterprise
 
#3

AW: hängende Interfaces

  Alt 29. Jan 2016, 15:33
DUnit Tests mit LeakCheck und PODOs nich als Interfaces nutzen.
Stefan
“Simplicity, carried to the extreme, becomes elegance.” Jon Franklin

Delphi Sorcery - DSharp - Spring4D - TestInsight
  Mit Zitat antworten Zitat
Benutzerbild von jaenicke
jaenicke

Registriert seit: 10. Jun 2003
Ort: Berlin
9.345 Beiträge
 
Delphi 11 Alexandria
 
#4

AW: hängende Interfaces

  Alt 29. Jan 2016, 22:52
Jetzt benötige ich aber diverse gegenseitige Referenzierungen und Eintragungen in diversen Listen.

EurekaLog zeigt mir entsprechend auch MemoryLeaks an, da die Objekte NATÜRLICH nicht aufgelöst werden.
So natürlich ist das nicht. Wir haben auch Interfaces, in denen andere Interfaces sich in einem Listener registriert haben um bei Ereignissen benachrichtigt zu werden, die aber das andere Interface kennen.
Aber da werden dann auch die Listen mit den Listenern beim Shutdown geleert und dann erst alles andere abgebaut, das gehört zum regulären Shutdown-Prozess der Anwendung.
Sebastian Jänicke
Alle eigenen Projekte sind eingestellt, ebenso meine Homepage, Downloadlinks usw. im Forum bleiben aktiv!
  Mit Zitat antworten Zitat
EgonHugeist

Registriert seit: 17. Sep 2011
187 Beiträge
 
Delphi 10.2 Tokyo Starter
 
#5

AW: hängende Interfaces

  Alt 30. Jan 2016, 05:55
@Stahli

Wie man das im einzelnen Tracken kann, weiß ich nicht, ich passe auf );

jedoch du kannst auch "weak" Referencen nutzen. eg. TContainedObject oder dir die Referencen selber abbilden.
Code:
private
 fWeakMyInterface: Pointer;
 fMyInterface: IMyInterface;

...

var
  MyInterface: IMyInterface;
begin
  MyInterface := TMyInterfacedObject.Create(Self);
  fWeakMyInterface:= Pointer(MyInterface);
end;
Dann jedoch mußt du aber auch Nachdem Erschaffen des Interfaces dir die Referenz merken und 'nen DeRegister code mit reinfummeln, wenn ein Interface aus dem Speicher geworfen wird.

Somit kann man zirkuläre Interface Referenzen auflösen, jedoch können sich beide immernoch gegenseitig referenzieren: IMyInterface(fWeakMyInterface).DoSomething;
Der Owner sollte die schwache Reference haben, das SubInterface sollte den Owner direkt als Interface referenziern, damit dein DeRegister nicht auf ein gelöschtes interface knallt.

Dann kannst du beim aufräumen des Owners mit zB. procedure TMyInterfacedObject.Close; das Schwach-Referenzierte Interface, falls <> nil, zuerst zu einem Close zwingen. Es Deregistriert sich zuerst beim Owner(fWeakMyInterface wird genillt) und dann löscht es die Interface Referenz zum Owner, und schon lößen sich alle voneinander, beide wissen, es gibt keine weiteren Referenzen mehr, und der Speicher wird frei gegeben.

Geändert von EgonHugeist (30. Jan 2016 um 06:23 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von stahli
stahli

Registriert seit: 26. Nov 2003
Ort: Halle/Saale
4.336 Beiträge
 
Delphi 11 Alexandria
 
#6

AW: hängende Interfaces

  Alt 1. Mär 2017, 22:38
Ich will das Thema nochmal aufgreifen...

Weak Referenzen kann ich mit XE3 nicht nutzen. Das Arbeiten mit Pointern finde ich auch nicht optimal.

DUnit und LeakCheck kann ich nicht wirklich nachvollziehen.


Ich habe daher mal ein kleines Projekt gebastelt, um einen Ansatz zu suchen.
Dafür habe ich ein Basisinterface ICleanUp und eine Basisklasse TCleanUp erstellt, die mögliche gegenseitige Referenzen abbauen können.

Man muss allerdings für alle betroffenen Interfaces/Objekte CleanUp(MyIntf) aufrufen.

Ich hänge das Projekt an (XE3, nur Quelle, ohne EurekaLog-Aktivierung).
Hier auch ein Video dazu: https://youtu.be/2yjGr2CoT5E

Mich würde mal Eure Meinung dazu interessieren sowie natürlich auch bessere Lösungen.

In dem Zusammenhang hatte ich überlegt, dass man (bzw. Emba) doch evtl. loggen könnte, welche Referenzen erzeugt und nicht wieder freigegeben werden. Man müsste dazu in _AddRef und _Release m.E. die Speicherstelle der Variable, die Speicherstelle des Objektes und die Quelltextstelle in einer Liste sammeln bzw. wieder löschen.
Einträge, die zum Schluss übrig bleiben, wurden nicht freigegeben.

Ich versuche mal, das zu skizzieren (A und B sind Interfaces, die sich gegenseitig referenzieren können, die Nr am Anfang ist eine Programmzeile):

begin
...
10: A1 := TA.Create
-> Eintrag: Adr(A1) $12345 / Adr(ObjektA1) $456789 / Zeile 10
11: B1 := TB.Create
-> Eintrag: Adr(B1) $12346 / Adr(ObjektB1) $45678A / Zeile 11
...
18: A1.B := B1
-> Eintrag: Adr(A1.fB) $23456 / Adr(ObjektB1) $45678A / Zeile 18
19: B1.A := A1
-> Eintrag: Adr(B1.fA) $23457 / Adr(ObjektA1) $456789 / Zeile 19
...
end
-> löschen: Adr(A1) $12345 / Adr(ObjektA1) $456789 / Zeile 10
-> löschen: Adr(B1) $12346 / Adr(ObjektB1) $45678A / Zeile 11

Also direkt in _AddRef und _Release müsste sowohl die Speicherstelle des zu referenzierenden Objektes bekannt sein als auch die Speicherstelle der Variablen. Dann könnten dort die skizzierten Einträge erzeugt werden und bei Vorliegen von Debug-Infos auch Bezüge zum Quelltext.

Im Beispiel werden beim Prozeduraussprung die Referenzen auf ObjektA1 und ObjektB1 ja je einmal verringert.
Der Debugger weiß ja auch, dass die Variablen A1 und B1 aus dem Scope fallen. Also könnte er erkennen, dass die ersten beiden Einträge obsolet sind und könnte diese löschen oder kennzeichnen.

Somit würden zum Programmende folgende zwei Einträge übrig bleiben:
Adr(A1.fB) $23456 / Adr(ObjektB1) $45678A / Zeile 18
Adr(B1.fA) $23457 / Adr(ObjektA1) $456789 / Zeile 19

Damit könnte man die nicht aufgelösten Referenzen sehr zuverlässig und komfortabel finden.
Emba müsste dazu entsprechende Logs in _AddRef und _Release ermöglichen.

Ist das so denkbar oder ist das in der Form unmöglich?



Ein Reference Tracing bietet übrigens AQTime Pro inzwischen an.
https://www.youtube.com/watch?v=y4qbNwuk2Qs
Man muss jedoch EurekaLog dazu ggf. im Projekt deaktivieren.
Also kann man mit EurekaLog hängende Referenzen finden, dieses dann ausschalten und die Ursache mit AQTime näher untersuchen.
Ich muss allerdings sagen, dass ich das auch nicht so sehr übersichtlich finde.
Angehängte Dateien
Dateityp: zip HangingReferences.zip (123,3 KB, 2x aufgerufen)
Stahli
http://www.StahliSoft.de
---
"Jetzt muss ich seh´n, dass ich kein Denkfehler mach...!?" Dittsche (2004)
  Mit Zitat antworten Zitat
Benutzerbild von Zacherl
Zacherl

Registriert seit: 3. Sep 2004
4.629 Beiträge
 
Delphi 10.2 Tokyo Starter
 
#7

AW: hängende Interfaces

  Alt 2. Mär 2017, 01:47
Also direkt in _AddRef und _Release müsste sowohl die Speicherstelle des zu referenzierenden Objektes bekannt sein als auch die Speicherstelle der Variablen.
Die Speicherstelle des Objektes kannst du über einen Hook von TInterfacedObject._AddRef bzw. TInterfaced._Release auf jeden Fall loggen (Self ). Die Speicherstelle der Variable wirst du aber nicht ermitteln können.
Dass Embacadero da irgendwas in die Richtung einbaut halte ich auch für recht unwahrscheinlich. Der automatische Aufruf von _AddRef und _Release ist sowieso schon ein "Hack".

Inzwischen denke ich, dass die automatische Referenzzählung für mich doch nicht so sinnvoll ist. Interfaces möchte ich zwar wegen der austauschbaren Funktionalitäten und einheitlicher öffentlicher Schnittstellen weiter nutzen, aber die Lebenszeit und gegenseitige Referenzen würde ich doch lieber wieder selbst verwalten.
Also falls du die Schnittstellen nur in deinem Delphi Programm verwendest und nicht z.b. aus einer C++ DLL heraus damit hantieren willst, dann könntest du die Interfaces auch durch abstrakte Klassen ersetzen.
Projekte:
- GitHub (Profil, zyantific)
- zYan Disassembler Engine ( Zydis Online, Zydis GitHub)
  Mit Zitat antworten Zitat
Benutzerbild von Stevie
Stevie

Registriert seit: 12. Aug 2003
Ort: Soest
4.008 Beiträge
 
Delphi 10.1 Berlin Enterprise
 
#8

AW: hängende Interfaces

  Alt 2. Mär 2017, 02:24
Imo leider eine einfache Sache total overengineered.

Dein Cleanup und CleanupRef is unnötig, einfach sowas wie IDisposable implementieren und
im Dispose die eigenen Interfaces sofern auch IDisposable Dispose aufrufen
und dann auf nil setzen und schon ist jegliche zirkuäre Referenz aufgelöst.

Im übrigen möchte ich mal behaupten, wenn man so massive zirkuläre Interface Referenzen hat,
dann stimmt was mit der Architektur nicht oder man überstrapaziert sie, wo sie nicht hingehören.
Stefan
“Simplicity, carried to the extreme, becomes elegance.” Jon Franklin

Delphi Sorcery - DSharp - Spring4D - TestInsight

Geändert von Stevie ( 2. Mär 2017 um 02:27 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von stahli
stahli

Registriert seit: 26. Nov 2003
Ort: Halle/Saale
4.336 Beiträge
 
Delphi 11 Alexandria
 
#9

AW: hängende Interfaces

  Alt 3. Mär 2017, 12:23
Die Speicherstelle der Variable wirst du aber nicht ermitteln können.
Dass Embacadero da irgendwas in die Richtung einbaut halte ich auch für recht unwahrscheinlich. Der automatische Aufruf von _AddRef und _Release ist sowieso schon ein "Hack".
Dass ich das nicht ermitteln kann dachte ich mir. Aber der Compiler weiß ja eigentlich, wohin er die Referenz schreibt (also wo der Speicherplatz der Variablen liegt). Das würde ich jedenfalls voraussetzen. Daher dachte ich, dass Emba einen entsprechenden Log erzeugen könnte.
Aber so genau kann ich das natürlich nicht wissen.


Also falls du die Schnittstellen nur in deinem Delphi Programm verwendest und nicht z.b. aus einer C++ DLL heraus damit hantieren willst, dann könntest du die Interfaces auch durch abstrakte Klassen ersetzen.
Mit Interfaces will ich auf jeden Fall arbeiten und i.d.R. auch die Referenzzählung nutzen. Für die Sonderfälle der gegenseitigen Referenzierungen muss man dann halt eine explizite Lösung finden.



@Stevie

Du hast Recht.

Ich habe mein ICleanUp nochmal etwas umgebaut. Es sollte jetzt eher dem entsprechen, was Du mit IDisposable gemeint hast.
Ich rufe jetzt einfach Kill(MyIntf) auf (analog FreeAndNil(MyObj)).
Die Funktion CleanUpRef habe ich mal noch drin gelassen, aber sie ist i.d.R. nicht erforderlich.

Die häufigen gegenseitigen Referenzen waren Singleton-InterfaceObjekte, die sich alle gegenseitig benötigen und die ich den anderen Objekten jeweils gegenseitig bekanntgegeben hatte (entweder im constructor oder durch spätere Registrierung.

Jetzt habe ich diese doch wieder global bereitgestellt, was die Referenzierungsorgie natürlich verringert.
Angehängte Dateien
Dateityp: pas uCleanUp.pas (2,4 KB, 2x aufgerufen)
Stahli
http://www.StahliSoft.de
---
"Jetzt muss ich seh´n, dass ich kein Denkfehler mach...!?" Dittsche (2004)
  Mit Zitat antworten Zitat
Benutzerbild von Stevie
Stevie

Registriert seit: 12. Aug 2003
Ort: Soest
4.008 Beiträge
 
Delphi 10.1 Berlin Enterprise
 
#10

AW: hängende Interfaces

  Alt 3. Mär 2017, 15:50
Ich würd das ja so implementieren:

Delphi-Quellcode:
unit DisposableObject;

interface

type
  IDisposable = interface
    ['{07751839-9A68-47C0-9116-68D0D5D7956F}']
    procedure Dispose;
  end;

  TDisposableObject = class(TObject, IInterface, IDisposable)
{$IFNDEF AUTOREFCOUNT}
  private const
    objDestroyingFlag = Integer($80000000);
    function GetRefCount: Integer; inline;
{$ENDIF}
  protected
{$IFNDEF AUTOREFCOUNT}
    [Volatile] FRefCount: Integer;
    class procedure __MarkDestroying(const Obj); static; inline;
{$ENDIF}
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    procedure Dispose; virtual;
  public
{$IFNDEF AUTOREFCOUNT}
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
    class function NewInstance: TObject; override;
    property RefCount: Integer read GetRefCount;
{$ENDIF}
  end;

procedure DisposeAndNil(var intf);

implementation

procedure DisposeAndNil(var intf);
var
  disposable: Pointer;
begin
  if Assigned(IInterface(intf)) and (IInterface(intf).QueryInterface(IDisposable, disposable) = 0) then
  begin
    IInterface(intf) := nil;
    IDisposable(disposable).Dispose;
    IDisposable(disposable) := nil;
  end;
end;

{ TDisposableObject }

{$IFNDEF AUTOREFCOUNT}

function TDisposableObject.GetRefCount: Integer;
begin
  Result := FRefCount and not objDestroyingFlag;
end;

class procedure TDisposableObject.__MarkDestroying(const Obj);
var
  LRef: Integer;
begin
  repeat
    LRef := TDisposableObject(Obj).FRefCount;
  until AtomicCmpExchange(TDisposableObject(Obj).FRefCount, LRef or objDestroyingFlag, LRef) = LRef;
end;

procedure TDisposableObject.AfterConstruction;
begin
  AtomicDecrement(FRefCount);
end;

procedure TDisposableObject.BeforeDestruction;
begin
  if RefCount <> 0 then
    Error(reInvalidPtr);
end;

procedure TDisposableObject.Dispose;
begin
end;

class function TDisposableObject.NewInstance: TObject;
begin
  Result := inherited NewInstance;
  TDisposableObject(Result).FRefCount := 1;
end;

{$ENDIF AUTOREFCOUNT}

function TDisposableObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

function TDisposableObject._AddRef: Integer;
begin
{$IFNDEF AUTOREFCOUNT}
  Result := AtomicIncrement(FRefCount);
{$ELSE}
  Result := __ObjAddRef;
{$ENDIF}
end;

function TDisposableObject._Release: Integer;
begin
{$IFNDEF AUTOREFCOUNT}
  Result := AtomicDecrement(FRefCount);
  if Result = 0 then
  begin
    Dispose;
    __MarkDestroying(Self);
    Destroy;
  end;
{$ELSE}
  Result := __ObjRelease;
{$ENDIF}
end;


end.
Der Sinn und die Implementierung von DisposeAndNil liegt darin, dass damit nil Sicherheit gewährleistet und Rekursivität verhindert wird.

Das ganze IFDEF Zeugs liegt daran, dass ich einfach stumpf TInterfacedObject aus System kopiert habe, weil ich nicht davon ableiten wollte/konnte,
da ich eine zusätzliche Zeile in _Release hinzugefügt habe (Dispose aufrufen vorm Destroy). In das Destroy wollt ich es nicht hinzufügen,
weil man dann möglicherweise rekursive Dispose Aufrufe während eines Destroy Vorgangs hätte und dann diese auch noch gegen eventuelle Aktionen im Destroy
absichern hätte müssen (z.B. Überprüfung auf nil von fList in TD).

Das führt zu implementierungen von Dispose in z.B. TA wie folgt:

Delphi-Quellcode:
procedure TA.Dispose;
begin
  inherited;
  DisposeAndNil(fB);
  DisposeAndNil(fC);
end;
Wichtig: Das ganze hat trotz Namensähnlichkeit nix mit TObject.DisposeOf zu tun. Das ist nämlich a) nicht überschreibbar b) macht auf nicht ARC nix anderes als Free aufzurufen und am wichtigsten c) ruft auf ARC nur den Destructor auf aber gibt den Speicher nicht frei. Die zurückgelassene Instanz ist dann ein Zombie, denn es wird nicht CleanupInstance aufgerufen so dass gemanagte nicht explizit auf leer/nil gesetzte Felder noch gesetzt sind (siehe RSP-14682).
Stefan
“Simplicity, carried to the extreme, becomes elegance.” Jon Franklin

Delphi Sorcery - DSharp - Spring4D - TestInsight

Geändert von Stevie ( 3. Mär 2017 um 15:57 Uhr)
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 11:05 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