Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi Destroy wird ohne Aufruf ausgeführt - total strange... (https://www.delphipraxis.net/118744-destroy-wird-ohne-aufruf-ausgefuehrt-total-strange.html)

s.h.a.r.k 14. Aug 2008 07:44


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

sirius 14. Aug 2008 08:02

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?

xaromz 14. Aug 2008 08:48

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

s.h.a.r.k 14. Aug 2008 09:44

Re: Destroy wird ohne Aufruf ausgeführt - total strange...
 
Zitat:

Zitat von xaromz
... Dann vermute ich, dass die automatische Referenzzählung zuschlägt.

Könntest du mir diesen Punkt näher erläutern? Denn das hört sich recht vielversprechend an! Ich leite eben von TInterfacedObject und einem eigenen Interface ab, sodass ich die drei Methode QueryInterface, _AddRef und _Release nicht selbst schreiben muss. Habe dazu im Internet nicht viel gefunden gehabt und daher wollte ich sie nicht selbst programmieren... vielleicht war das nicht ganz geschickt. Kann man diese Methoden eigentlich auch leer lassen, bzw was wären die Konsequenzen dies bzgl.? Kenne mich mit Interfaces nicht wirklich aus.

Zur Näheren Erklärung:
  • ObjectMananger beinhaltet Daten-Objekte
  • zu jedem Daten-Objekt kann eine Form erzeugt werden
  • wird eine Form erzeugt, so wird automatisch im Timer-Manager ein neues Objekt erzeugt mit einer Referenz auf das passende Daten-Objekt
  • wird die Form geschlossen soll das Object im Timer-Manager zerstört werden, aber nicht das Daten-Objekt

Würde es evtl. etwas bringen, wenn ich den Quellcode so lasse und zuvor die Referenz im Objekt des Timer-Managers auf nil setze?

Flocke 14. Aug 2008 10:02

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:
type
  ISomeInterface = interface
  end;

  TSomeInterface = class(TInterfacedObject, ISomeInterface)
  end;

var
  i: ISomeInterface;

begin
  i := TSomeInterface.Create(...) as ISomeInterface;
end;
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:
Delphi-Quellcode:
  i := nil;
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.

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.

s.h.a.r.k 14. Aug 2008 10:15

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:
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;
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?!

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

Flocke 14. Aug 2008 11:25

Re: Destroy wird ohne Aufruf ausgeführt - total strange...
 
Zitat:

Zitat von s.h.a.r.k
Ich bin nun einfach mal so dreist und nutze deinen Quellcode, da der als Beispiel sehr gute passt ;)

...ich schreibe dann mal weiter...

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:
procedure blaaa(aObject: TObject);
var
  tmp : ISomeInterface; // <-!
begin
  tmp := TSomeInterface.Create();
  tmp.registerObject(blub);

  // hier kommt nun die tolle Stelle
//FreeAndNil(tmp); // <-!
end;
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:
Delphi-Quellcode:
  TSomeInterface = class(TInterfacedPersistent, ISomeInterface) // <-!
  public
    FVar : TObject;
    procedure registerObject(aObject: TObject);
  end;
Es hängt also davon ab, welchen Zweck dein Objekt bzw. dein Interface erfüllen soll.

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:
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;
Merkregel: Nachdem du ein von TInterfacedObject abgeleitetes Objekt einmal als Interface benutzt hast, solltest du es nicht mehr über die Objektreferenz ansprechen.

Zitat:

Zitat von s.h.a.r.k
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!?

Tut mir leid, ein gutes Tutorial dazu habe ich auch noch nicht gefunden.

xaromz 14. Aug 2008 17:25

Re: Destroy wird ohne Aufruf ausgeführt - total strange...
 
Hallo,

Tutorial Interfaces.

Gruß
xaromz


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