Destroy wird ohne Aufruf ausgeführt - total strange...
Guten Morgen,
ich habe seit gestern Abend das seltsame Problem, dass der Destructor Destroy() einer Klasse ausgeführt wird, ohne ihn aber gerufen zu haben, auch nicht mit FreeAndNil() oder dergleichen. Anschließend landet meine Anwendung in einer Endlosschleife und meldet eine Exception "Stack-Überlauf". Ich habe das folgende Konstrukt: ich lade aus der Datenbank Datensätze und erzeuge daraus ein paar Daten-Objekte, welche ich in einer TList "speichere". Ich kann dann jeweils eine Form zu einer dieser Objekte erzeugen, über welche ich dann die Werte bearbeiten kann. Der Form übergebe ich eben eine Referenz. Nun habe ich parallel dazu einen Timer-Manager geschrieben, welcher in regelmäßigen Abständen Werte in der Datenbank ändert. Wird nun eine Form zu einem Datensatz geöffnet registriere ich das Daten-Objekt beim Timer-Manager, was ich so realisiert habe, dass ein passendes Objekt erzeugt wird, welches als Referenz das Daten-Objekt erhält, um das neu erzeugte Objekt später wieder passend auffindbar zu machen. Dieses neue Objekt schiebe ich dann in eine TList vom Timer-Manager. Jetzt kommt das Problem und die Situation die ich nicht verstehe: wenn ich nun die Form schließe, dann teile ich dem Timer-Manager in der OnClose()-Methode mit, dass er doch bitte das Objekt in seiner TList, passend zum Daten-Objekt, freigeben soll. Ich suche mir das passende Objekt heraus, dann folgt ein TList.Delete() und anschließend ein FreeAndNil() auf dieses Objekt. Davon dürfte ja das Daten-Objekt nicht betroffen sein, allerdings wird genau da danach dann die Destroy()-Methode des Daten-Objekts aufgerufen und ich habe keine Ahnung wieso :wall: es kann ja wohl nicht an der Referenz liegen, da sonst sämtliche Datenstrukturen in meiner Anwendung Schrott wären und bisher hat es ja auch so funktioniert. Vielleicht hilft es noch, wenn ich euch mitteile, dass die Klasse zum Datenobjekt und die Objekte im Timer-Manager vom selben Interface abgeleitet sind, was aber nicht das Problem sein darf, da dies ja nur eine Vorschrift für die Klasse ist, oder!? Ich weiß grad selbst nicht mehr recht ein und aus... das kann es echt nicht sein. PS: ich würde ja gerne einen Quelltext exportieren, allerdings ist das Projekt sehr umfangreich und es würde zu lange dauern. Außer ich komme eben nie auf eine Lösung, dann muss ich das wohl tun. Mit freundlichen Grüßen Armin |
Re: Destroy wird ohne Aufruf ausgeführt - total strange...
Dass du nicht den Quelltext kopieren kannst, verstehe ich. Aber könntest du vielleicht eine UML-Skizze o.ä. anhängen? Ich komme bei der Klassenbeschreibung nicht ganz mit.
Edit 1: (vielleicht habe ich es jetzt doch verstanden) Du hast also in deinem Timer-Manager eine Liste welches u.a. ein Objekt1 enthält, indem ein Objekt2 enthalten ist (was dein Daten-Objekt ist). Nun willst du Objekt1 löschen aber Objekt2 behalten (weil woanders noch referenzen darauf sind). Und aus irgendwelchen Gründen löscht Objekt1, Objekt2 gleich mit. Hmm? Von was sind sie den abgeleitet. Ich könnte mir jetzt z.B. TComponent o.ä. als Vorfahr vorstellen. Edit2: Kannst du nicht einen Breakpoint in den Destructor setzen und schauen wohin er zurückspringt? |
Re: Destroy wird ohne Aufruf ausgeführt - total strange...
Hallo,
habe ich das richtig verstanden, dass Du Interfaces verwendest? Dann vermute ich, dass die automatische Referenzzählung zuschlägt. Gruß xaromz |
Re: Destroy wird ohne Aufruf ausgeführt - total strange...
Zitat:
Zur Näheren Erklärung:
Würde es evtl. etwas bringen, wenn ich den Quellcode so lasse und zuvor die Referenz im Objekt des Timer-Managers auf nil setze? |
Re: Destroy wird ohne Aufruf ausgeführt - total strange...
TInterfacedObject ist für die Fälle gedacht, wo dein Objekt nach der Initialisierung nur noch über das Interface angesprochen wird, also z.B. ganz abstrakt:
Delphi-Quellcode:
Ab der Zuweisung kannst du das Objekt (TSomeInterface) vergessen. Du musst es nicht mehr freigeben - das geschieht automatisch, wenn der Referenzzähler 0 erreicht. Delphi erhöht/vermindert diesen Referenzzähler automatisch bei der Übergabe von Parametern und bei der Zuweisung an Variablen, im Beispiel oben würdest du das Objekt also einfach durch:
type
ISomeInterface = interface end; TSomeInterface = class(TInterfacedObject, ISomeInterface) end; var i: ISomeInterface; begin i := TSomeInterface.Create(...) as ISomeInterface; end;
Delphi-Quellcode:
wieder freigeben. Noch "automatischer" ist dies bei lokalen Variablen, für die Delphi automatisch einen try-finally-Block erzeugt, und die beim Verlassen der Prozedur/Funktion dann automatisch freigegeben werden.
i := nil;
Willst du das nicht, dann solltest du TInterfacedPersistent als Basisklasse nehmen. Hier muss das Objekt manuell wieder freigegeben werden. Dies ist für Klassen gedacht, die nicht ausschließlich der Implementierung eines Interfaces dienen sondern ein Interface ggf. einfach nur "zusätzlich" implementieren möchten. Außerdem werfe ich nochmal die beiden Klassen TAggregatedObject und TContainedObject ins Rennen, die man als Basisklasse für Objekte mit Interfaces nehmen kann, die zu einem anderen Objekt mit Interface gehören (z.B. Elemente einer Aufzählung) und ihre Referenzzählung an das "besitzende" Objekte delegieren. |
Re: Destroy wird ohne Aufruf ausgeführt - total strange...
Ich bin nun einfach mal so dreist und nutze deinen Quellcode, da der als Beispiel sehr gute passt ;)
Delphi-Quellcode:
So hab ich das in meinem Quellcode gemacht. Ich weiß nicht, was daran falsch sein soll bzw. es ist ja in so fern falsch, dass ab nach dem FreeAndNil(tmp) dann das Objekt blub ebenfalls zerstört wird. Auf das Objekt blub, welches irgendwo anders erzeugt worden ist, zeigt aber definitiv noch eine Referenz, da es in einer Liste (TList) eingetragen wurde. Ich weiß nun nicht, was genau das Interface mit meinem Objekten anstellt. Es kann ja nicht sein, dass auf alle Referenzen innerhalb es tmp-Objekts ein Free() oder FreeAndNil() automatisch angewendet wird?!
type
ISomeInterface = interface procedure registerObject(aObject: TObject); end; TSomeInterface = class(TInterfacedObject, ISomeInterface) public FVar : TObject; procedure registerObject(aObject: TObject); end; procedure TSomeInterface.registerObject(aObject: TObject); begin FVar := aObject; end, // ... procedure blaaa(aObject: TObject); var tmp : TSomeInterface; begin tmp := TSomeInterface.Create(); tmp.registerObject(blub); // hier kommt nun die tolle Stelle FreeAndNil(tmp); end; PS: Kennst du ein gutes Tutorial oder dergleichen, wo man das nachlesen kann?! Habe bisher eben noch kein gutes gefunden, wo drin stehst was wann und warum passiert!? mit freundlichen Grüßen Armin |
Re: Destroy wird ohne Aufruf ausgeführt - total strange...
Zitat:
Wie ich oben schon schrieb: wenn das Objekt nur das Interface implementieren soll, dann benutze auch nur das Interface und nicht die Referenz auf das Objekt (s.u.). Solche zählenden Interfaces werden automatisch freigegeben, sobald sie nicht mehr benötigt werden. Variante 1 wäre also einfach, ISomeInterface an Stelle von TSomeInterface zu benutzen:
Delphi-Quellcode:
Soll dein Objekt die Schnittstelle nur "nebenbei" mit implementieren, also nicht hauptsächlich oder ausschließlich dem Interface dienen, dann solltest du ganz einfach von TInterfacedPersistent (oder TComponent) ableiten:
procedure blaaa(aObject: TObject);
var tmp : ISomeInterface; // <-! begin tmp := TSomeInterface.Create(); tmp.registerObject(blub); // hier kommt nun die tolle Stelle //FreeAndNil(tmp); // <-! end;
Delphi-Quellcode:
Es hängt also davon ab, welchen Zweck dein Objekt bzw. dein Interface erfüllen soll.
TSomeInterface = class(TInterfacedPersistent, ISomeInterface) // <-!
public FVar : TObject; procedure registerObject(aObject: TObject); end; Von TInterfacedObject abgeleitete Klasse sind -wie oben schon geschrieben- eigentlich für die ausschließliche Verwendung als Interfaces gedacht. Das Problem ist die Referenzzählung, die das Objekt automatisch freigibt, sobald der Zähler auf 0 abfällt (nachdem er mindestens ein Mal auf 1 hochging):
Delphi-Quellcode:
Merkregel: Nachdem du ein von TInterfacedObject abgeleitetes Objekt einmal als Interface benutzt hast, solltest du es nicht mehr über die Objektreferenz ansprechen.
var
T: TSomeInterface; I: ISomeInterface; begin T := TSomeInterface.Create; // Zähler: 0 I := T as ISomeInterface; // Zähler: 1 I := nil; // Zähler: 0 --> Objekt wird freigegeben // ab hier ist T nicht mehr gültig end; Zitat:
|
Re: Destroy wird ohne Aufruf ausgeführt - total strange...
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 05:43 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