![]() |
Delphi-Version: 5
Interface und Objektreferenz offenbar noch nicht verstanden
Hallo, zusammen,
offenbar habe ich das Prinzip von Interfaces und Reference-Counting noch nicht ganz verstanden - zumindest, wenn ich davon ausgehe, dass das Problem mal wieder vor dem Monitor steckt. Ich weiß, warum mein Ansatz nicht funktioniert, aber nicht, wie er funktionieren sollte. Das Ziel: Ich habe eine Datenstruktur, die ich oft mit unterschiedlichen Aufgaben durchlaufen will. Also so eine Art ein Callback-Thema. Meine Idee war nun, dass diese Datenstruktur eine Routine CallEnumeratorForAllElements bekommt, die als Parameter ein Interface erwartet. Dieses wird dann für jedes Element in der Struktur aufgerufen. Zunächst das Interface:
Delphi-Quellcode:
IPlanDataEnumerator = Interface
['{BDB4BEB2-FFE4-429A-9007-4DA7D235E92A}'] procedure HandlePlanDataElement(PD: TPlanData); End; Ein Objekt, das dieses Interface implementiert, ist beispielsweise TMySummary. Die Methode HandlePlanDataElement zählt die Elemente unter bestimmten Bedingungen und erhöht dabei zwei Objektvariablen (Integer). Wenn ich das Objekt aber wie nachstehend beschrieben verwende, erhalte ich bei Free eine Zugriffsverletzung:
Delphi-Quellcode:
var
Summary: TMySummary; begin Summary:=TMySummary.Create; Summary.Init; ProjectData.CallEnumeratorForAllElements(Summary); StatusBar.Panels[1].Text:='Unassigned Order Packages: '+IntToStr(Summary.CountUnassignedOrderPackages); StatusBar.Panels[2].Text:='Unassigned Orders: '+IntToStr(Summary.CountUnassignedOrders); Summary.Free; end; Im Debugger bekomme ich für Summary vor dem Aufruf von CallEnumeratorForAllElements noch FRefCount=0, danach einen Wert von -2147483648. Kann ich vestehen, wie schreibt schon Nick Hodges: "...you should never, ever mix interface references with "real" object rereferences to an implementing class. Never." - OK, Nick, but how the hell do we do it then? Hier laufe ich offenbar in die Reference-Count-Falle, so dass mein TSummary-Object vor dem Aufruf von Free schon längst automatisch freigegeben wurde (und mithin auch die Zählwerte für die Tonne sind). Ich könnte Summary oben natürlich als IPlanDataEnumerator definieren, aber dann geht mir ja der Zugriff auf die beiden Zählvariablen verloren. Und im Sinne eines möglichst universellen Interface wäre es unsinnig, die Zählfunktionen zu implementieren, oder? Wenn ich bei der Verwendung eines Interface den Rest des implementierenden Objekts nicht nutzen kann, könnte ich gleich auf das Interface verzichten und direkt ein Objekt nutzen. Je mehr ich dazu lese, desto mehr verwirrt das :( |
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Du solltest noch die Deklaration von TMySummary liefern, denn die spielt hier eine entscheidende Rolle.
|
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Delphi-Quellcode:
IPlanDataEnumerator = Interface
['{BDB4BEB2-FFE4-429A-9007-4DA7D235E92A}'] procedure HandlePlanDataElement(PD: TPlanData); procedure Init; // <--- falls alle anderen Klassen das auch brauchen, ansonsten in das entsprechende Interface verschieben End; ISummary = interface(IPlanDataEnumerator) [Irgendeine_GUID] function GetCountUnassignedOrderPackages : Integer; function GetCountUnassignedOrders : Integer; property CountUnassignedOrderPackages : Integer read GetCountUnassignedOrderPackages; property CountUnassignedOrders : Integer read GetCountUnassignedOrders; end;
Delphi-Quellcode:
TMySummary = class(TInterfacedObject, IPlanDataEnumerator, ISummary)
...
Delphi-Quellcode:
Viel Spaß!
var
Summary: ISummary ; begin Summary := TMySummary.Create; Summary.Init; ProjectData.CallEnumeratorForAllElements(Summary); StatusBar.Panels[1].Text:='Unassigned Order Packages: '+IntToStr(Summary.CountUnassignedOrderPackages); StatusBar.Panels[2].Text:='Unassigned Orders: '+IntToStr(Summary.CountUnassignedOrders); end; |
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Ein paar Regeln die es zu beachten gibt:
1. Ein referenzgezähltes Objekt ist referenzgezählt. Es wird niemals manuell destroyed (Aufruf von .Free), es wird dann automatisch destroyed , wenn es keine Referenzen mehr auf das Objekt gibt. 2. Eine Variable für ein referenzgezähltes Objekt ist immer vom Typ eines der von der Klasse implementierten Interfaces (entweder ISummary oder IPlanDataEnumerator) und niemals von der Klasse (TMySummary) selbst. 3. Es dürfen nur die Instanzmethoden aufgerufen werden, die über ein entsprechendes Interface definiert wurden (ISummary oder IPlanDataEnumerator) und niemals Instanzmethoden die ausschließlich über die implementierende Klasse bereitgestellt (TMySummary), aber in keinem der implementierten Interfaces definiert sind. Einzige Ausnahme sind Aggregated & Contained Objects |
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Erst einmal vielen Dank für die bisherigen Ratschläge! Hier noch die Deklaration von TMySummary
Delphi-Quellcode:
Und hier die Implementierung:
TMySummary = class(TInterfacedObject, IPlanDataEnumerator)
public CountUnassignedOrderPackages, CountUnassignedOrders: Integer; procedure Init; procedure HandlePlanDataElement(PD: TPlanData); end;
Delphi-Quellcode:
procedure TMySummary.Init;
begin Self.CountUnassignedOrderPackages:=0; Self.CountUnassignedOrders:=0; end; procedure TMySummary.HandlePlanDataElement(PD: TPlanData); begin if PD.IsOrderPackage then begin if TOrderPackage(PD).LPOCostType='' then Inc(Self.CountUnassignedOrderPackages); end; if PD.IsOrder then begin if TOrder(PD).SAPCostType='' then Inc(Self.CountUnassignedOrders); end; end; Den Ansatz mit zwei Interfaces werde ich nachher gleich mal probieren, wobei ich erstaunt bin, dass man ein ISummary definieren kann und das dann als IPlanEnumerator übergeben kann. |
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Hi,
der Ansatz mit zwei Interfaces funktioniert leider nicht. Da beschwert sich der Compiler verständlicherweise über inkompatible Typen (ISummary und IPlanDataEnumerator).
Delphi-Quellcode:
TMySummary = class(TInterfacedObject, IPlanDataEnumerator, ISummary) (...) procedure Tfrm_Main.UpdateStatusBar; var Summary: ISummary; begin Summary:=TMySummary.Create; Summary.Init; ProjectData.CallEnumeratorForAllElements(Summary); // <<<<< Hier wird ISummary als IPlanDataEnumerator übergeben StatusBar.Panels[1].Text:='Unassigned Order Packages: '+IntToStr(Round(Summary.GetSummaryResult)); end; |
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Zitat:
|
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Zitat:
|
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Zitat:
|
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Zitat:
Das funktioniert jetzt auch so:
Delphi-Quellcode:
Aber die Variante mit einem spezielleren Enumerator-Interface ist natürlich sauberer. Werde ich nochmal so umsetzen. Die Umsetzung oben gefällt mir nicht wirklich, ist ein wenig so wie eine unverputzte Hauswand.
procedure Tfrm_Main.UpdateStatusBar;
var Summary: IPlanDataEnumerator; begin Summary:=TMySummary.Create; TMySummary(Summary).Init; ProjectData.CallEnumeratorForAllElements(Summary); StatusBar.Panels[1].Text:='Unassigned Order Packages: '+IntToStr(TMySummary(Summary).CountUnassignedOrderPackages); StatusBar.Panels[2].Text:='Unassigned Orders: '+IntToStr(TMySummary(Summary).CountUnassignedOrders); end; |
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Und an alle: Besten Dank! :-D
|
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Zitat:
Das harte casten auf die Klasse ist suboptimal. Wenn du möchtest, dass ein klarer(er) Bezug zu IPlanDataEnumerator besteht kannst du es auch so schreiben:
Delphi-Quellcode:
procedure Tfrm_Main.UpdateStatusBar;
var Summary: ISummary; begin Summary:=TMySummary.Create; Summary.Init; ProjectData.CallEnumeratorForAllElements(Summary as IPlanDataEnumerator ); // <<<<< Hier wird ISummary als IPlanDataEnumerator übergeben StatusBar.Panels[1].Text:='Unassigned Order Packages: '+IntToStr(Round(Summary.GetSummaryResult)); end; |
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Zitat:
Warum mache ich das... gute Frage. Das Problem ist, dass TMySummary eben ein umfangreiches Objekt ist, das mehrere unterschiedliche Interfaces implementieren sollte. Ich bin schon unglücklich, es als IPlanDataEnumerator zu deklarieren. Eigentlich müsste es heißen
Delphi-Quellcode:
Ich bin auf das Problem gestoßen, als ich einfach nur "Summary" an die Prozedur übergeben habe - in der Annahme, dass dieses Casting automatisch erfolgt. Dann aber gibt Delphi das Objekt zu früh frei. Mit dem Cast passiert das nicht - aber ich verstehe nicht wirklich warum. Und das fühlt sich ziemlich blöd und unstabil an.
var
Summary: TMySummary; begin Summary:=TMySummary.Create; Summary.Init; ProjectData.CallEnumeratorForAllElements(Summary as IPlanDataEnumerator); StatusBar.Panels[1].Text:='Unassigned Order Packages: '+IntToStr(Summary.CountUnassignedOrderPackages); StatusBar.Panels[2].Text:='Unassigned Orders: '+IntToStr(Summary.CountUnassignedOrders); end; Der Ansatz, Interfaces zu nutzen, die voneinander erben, hilft hier zwar gerade noch, aber löst das Grundproblem nicht. Ich habe noch kein Beispiel gefunden, das den Umgang mit Objekten zeigt, die mehrere unabhängige Interfaces implementieren. Nehmen wir mal ein anderes Beispiel: TAutomobil könnte IFarbe, ISteuerung und IMotor implementieren, die nichts miteinander zu tun haben (IFarbe könnte auch von TBauklotz implementiert werden, ISteuerung von TFahrrad, IMotor aber nicht). Nehmen wir an, wir wollen das Objekt TAutomobil nun umspritzen:
Delphi-Quellcode:
procedure Umspritzen(Gegenstand: IFarbe);
[...] Umspritzen(TAutomobil) Dann ergibt es ja keinen Sinn, das Auto mit "var Auto: IFarbe" zu deklarieren. Wir wollen ja das ganze Auto nutzen. Mein erster Ansatz, einfach das Objekt zu übergeben, sah so aus:
Delphi-Quellcode:
Das führt offenbar dazu, dass ein neues Interface angelegt wird und dann wieder freigegeben wird. Auto scheint nach dem Aufruf von Umspritzen zumindest freigegeben zu sein. Außerdem meine ich beobachtet zu haben, dass die Änderungen am übergegeben Gegenstand nur für das Interface gelten. Man kann innerhalb von Umspritzen dann mit TObject(Gegenstand) arbeiten, dann werden wirklich die Objektattribute geändert. Alles nicht hübsch und wirkt wacklig. Wie es aber offiziell laufen sollte, konnte ich noch nicht sicher herausfinden. Funktioniert eben alles, wirkt aber brüchig.
var
Auto: TAutomobil: [...] Auto:=TAutomobil.Create; Umspritzen(Auto); Um noch ein Beispiel zu nehmen: Ich habe ein Benachrichtigungsobjekt, bei dem sich andere Objekte als Interessent für bestimmte Nachrichten eintragen können, sofern sie IMessageHandler implementieren. Das können Formulare, TInterfacedObject-Nachfahren oder auch sonstige Controls sein. Wenn irgendwo in der Anwendung z.B. ein Budgetwert geändert wird, sorgt das Benachrichtigungsobjekt dafür, dass die Handler-Methode aller eingetragenen Objekte aufgerufen wird. Ich verzichte auf Windows-Messages, weil ich die Ausführung abwarten und die Reihenfolge sehr gezielt steuern will. Wie auch immer: Auch hier habe ich den Fall, dass ich ein Objekt, dass ein bestimmtes Interface implementiert, an eine Funktion übergebe, die genau dieses Interface als Parameter erwartet. Die perfekte Funktionsdeklaration wäre also:
Delphi-Quellcode:
procedure DoSomethingWithAnObject(Object: "Object which Implements ISomeInterface");
Mal so in Pidgin-Delphi... weil das nicht geht, ist der Parameter vom Typ Interface und es stellt sich die Frage, wie man dann das Objekt korrekt übergibt. "Objekt as ISomeInterface" scheint zu funktionieren, "Objekt" nicht, das führt zur Freigabe des Objekts. |
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Wenn Du mit Interfaces arbeitest solltest Du ab dem Moment nicht mehr mit der Klasse arbeiten.
Wenn Du z.B. eine Autoklasse hast:
Delphi-Quellcode:
dann kannst Du später eine Objekt als Interface verwenden:
TCar = class(TInterfacedObject, IEngine, IColor, IDriver)
Delphi-Quellcode:
Dieses kannst Du dann prüfen, ob es auch andere Interfaces implementiert:
var Intf: IIinterface;
Intf := TCar.Create;
Delphi-Quellcode:
Ab dem Moment kannst bzw. solltest Du nicht mehr mit der Klasse arbeiten. Jedenfalls nicht von außen auf diese zugreifen.
var lColor: IColor;
lEngine: IEngine; if Supports(Intf, IColor, lColor) then lColor.Color := clRed; if Supports(Intf, IEngine, lEngine) then lEngine.Start; In Deinem Beispiel verstehe ich nicht wirklich, warum Du Summary als Interface ausführen willst. Das könnte doch auch einfach eine Klasse oder ein Record bleiben, der die Zählwerte sammelt. In Interface bringt Vorteile, - wenn Du Dich nicht mehr um die Freigabe des Objektes kümmern willst oder - wenn Du unterschiedliche (nicht voneinander abgeleitete) Klassen hast, die die gleichen Funktionalitäten implementieren sollen (also an bestimmten Stellen austauschbar sein müssen). Du kannst ja mal schauen, ob Dir mein Tutorial etwas bringt: ![]() Die Videos vermutlich nicht, aber evtl. die Diskussion dort. |
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Zitat:
Delphi-Quellcode:
Und das macht einen deutlichen Unterschied. Dito mit Steuerung und Farbe (wobei Farbe wieder eine Eigenschaft von Karosserie wäre, weil die Inneneinrichtung ja auch ne Farbe (mehrere) hat).
type
TAutomobil = class(xyz) private FMotor: IMotor .... Prüf mal dein TSummary - ich vermute Du hast hier ein vergleichbares Problem: EIn Superobjekt, das zu viele Zuständigkeiten hat und ggf. anders aufgeteilt werden sollte... |
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Zitat:
Verwende NICHT die Objektinstanzen, sondern nur die Interfaceinstanzen. Vergleiche den von mir zitierten Abschnitt nochmal ganz genau mit meinen Quelltext aus dem Post davor (Tipp: Schaue die Variablen-Deklaration an). Und ggf. auch nochmal zum nachlesen: ![]() |
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Zitat:
Sorry für den vielen Text - ich wollte nur den Hintergrund schildern, weswegen mir abgeleitete Interfaces nicht reichen und ich gerne auch auf Objektmethoden zugreifen würde. Den Text von Nick kannte ich - das Buch liegt hier - aber der beschreibt leider nur die einfachen Fälle. Und dass Du die Variable als Interface deklariert hattest, hatte ich gesehen, das Casting fand ich nur unglücklich. Wenn man ein TForm mit Interfaces ausstatten will, müsste man zudem ein Interface schreiben, das alle von außen genutzten Methoden (z.B. ShowModal) beinhaltet, damit man das Formular anzeigen kann, es weitere Interface-Methoden ansprechen kann und Reference Count dennoch funktioniert. Ergibt sicher Sinn, die Logik "wenn ein Objekt irgendein Interface implementiert, dann sollten auch alle anderen Methoden des Objekts, die man nutzen will, über ein Interface verwendet werden" war mir noch nicht so klar. Nochmals besten Dank! |
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Zitat:
Hier kann auch einfach geprüft (is-Operator) und gecastet werden (as-Operator), ohne das dir irgendetwas kaputt geht, weil der Reference Counting Mechanismus für TComponent anderes implementiert ist, als bspw. für TInterfaceObject. Wenn es also heißt:
Delphi-Quellcode:
Dann kannst du problemlos folgendes tun:
type
TDeineForm = class(TCustomForm, IEineFunktionalität, IEineAndereFunktionalität) ...
Delphi-Quellcode:
var
MeineInstanz : IEineFunktionalität; LokalesForm : TForm; begin ... if MeineInstanz is TForm then begin LokalesForm := MeineInstanz as TForm; // oder TForm(MeineInstanz), wenn wirklich durch is-Operator oder Supports(...) geprüft wurde LokalesForm.ShowModal; ... end; Zitat:
Das wäre sonst sehr sehr falsch! |
AW: Interface und Objektreferenz offenbar noch nicht verstanden
Das mit dem Casten sollte man sich aber auch genau überlegen und nicht als generellen UseCase abspeichern.
Es gibt Fälle, wo man das gefahrlos machen kann und andere, wo man damit auf die Nase fällt. Generell sollte ein Interface alles das liefern was benötigt wird um damit zu arbeiten. Ansonsten fragt man einfach per ![]() Aber es macht keinen Sinn ein Monster-Interface zu erschaffen. Besser sind dort kleine aber feine, die man dann an unterschiedliche Klassen hängen kann.
Delphi-Quellcode:
IShowView = interface
function GetVisible; property Visible: Boolean read GetVisible; procedure Show; procedure Hide; end; IShowModalView = interface function ShowModal: TModalResult; end; |
Alle Zeitangaben in WEZ +1. Es ist jetzt 21:20 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-2025 by Thomas Breitkreuz