![]() |
hängende Interfaces
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. ![]() ![]() Ich werde jetzt mal in Richtung von Bummis Vorschlag überlegen: ![]() 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:
sondern
TPerson.NameDesVaters: string
begin Result := Vater.Name; end;
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. |
AW: hängende Interfaces
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. |
AW: hängende Interfaces
DUnit Tests mit
![]() |
AW: hängende Interfaces
Zitat:
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. |
AW: hängende Interfaces
@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:
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.
private
fWeakMyInterface: Pointer; fMyInterface: IMyInterface; ... var MyInterface: IMyInterface; begin MyInterface := TMyInterfacedObject.Create(Self); fWeakMyInterface:= Pointer(MyInterface); end; 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. |
AW: hängende Interfaces
Liste der Anhänge anzeigen (Anzahl: 1)
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. :oops: 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: ![]() 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. ![]() 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. |
AW: hängende Interfaces
Zitat:
Delphi-Quellcode:
bzw.
TInterfacedObject._AddRef
Delphi-Quellcode:
auf jeden Fall loggen (
TInterfaced._Release
Delphi-Quellcode:
). Die Speicherstelle der Variable wirst du aber nicht ermitteln können.
Self
Dass Embacadero da irgendwas in die Richtung einbaut halte ich auch für recht unwahrscheinlich. Der automatische Aufruf von
Delphi-Quellcode:
und
_AddRef
Delphi-Quellcode:
ist sowieso schon ein "Hack".
_Release
Zitat:
|
AW: hängende Interfaces
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. |
AW: hängende Interfaces
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:
Aber so genau kann ich das natürlich nicht wissen. Zitat:
@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. |
AW: hängende Interfaces
Ich würd das ja so implementieren:
Delphi-Quellcode:
Der Sinn und die Implementierung von DisposeAndNil liegt darin, dass damit nil Sicherheit gewährleistet und Rekursivität verhindert wird.
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. 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:
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
procedure TA.Dispose;
begin inherited; DisposeAndNil(fB); DisposeAndNil(fC); end; ![]() |
Alle Zeitangaben in WEZ +1. Es ist jetzt 02:49 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz