Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Interface und Objektreferenz offenbar noch nicht verstanden (https://www.delphipraxis.net/186633-interface-und-objektreferenz-offenbar-noch-nicht-verstanden.html)

Hepdepaddel 18. Sep 2015 07:34

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 :(

Uwe Raabe 18. Sep 2015 08:11

AW: Interface und Objektreferenz offenbar noch nicht verstanden
 
Du solltest noch die Deklaration von TMySummary liefern, denn die spielt hier eine entscheidende Rolle.

TiGü 18. Sep 2015 09:22

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:
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;
Viel Spaß!

alda 18. Sep 2015 11:51

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

Hepdepaddel 18. Sep 2015 13:18

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:
  TMySummary = class(TInterfacedObject, IPlanDataEnumerator)
    public
      CountUnassignedOrderPackages, CountUnassignedOrders: Integer;
      procedure Init;
      procedure HandlePlanDataElement(PD: TPlanData);
  end;
Und hier die Implementierung:

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.

Hepdepaddel 18. Sep 2015 13:36

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;

TiGü 18. Sep 2015 13:47

AW: Interface und Objektreferenz offenbar noch nicht verstanden
 
Zitat:

Zitat von Hepdepaddel (Beitrag 1316248)
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.

Ist ja voneinander abgeleitet!

TiGü 18. Sep 2015 13:51

AW: Interface und Objektreferenz offenbar noch nicht verstanden
 
Zitat:

Zitat von Hepdepaddel (Beitrag 1316251)
der Ansatz mit zwei Interfaces funktioniert leider nicht. Da beschwert sich der Compiler verständlicherweise über inkompatible Typen (ISummary und IPlanDataEnumerator).

Hast du ISummary von IPlanDataEnumerator abgeleitet?

alda 18. Sep 2015 14:08

AW: Interface und Objektreferenz offenbar noch nicht verstanden
 
Zitat:

Zitat von Hepdepaddel (Beitrag 1316251)
der Ansatz mit zwei Interfaces funktioniert leider nicht. Da beschwert sich der Compiler verständlicherweise über inkompatible Typen (ISummary und IPlanDataEnumerator).

Eine Klasse kann beliebig viele Interfaces implementieren, das geht schon. Wenn die Interfaces nicht voneinander ableiten und ein impliziter Cast nicht möglich ist, dann musst Du das explizit casten (AS). Allerdings sollte man auch mal schaun ob deine Implementierung insgesamt so Sinn macht.

Hepdepaddel 18. Sep 2015 16:06

AW: Interface und Objektreferenz offenbar noch nicht verstanden
 
Zitat:

Zitat von TiGü (Beitrag 1316252)
Ist ja voneinander abgeleitet!

Argh... gucken müsste man können. Freitag, wird Zeit fürs Wochenende.

Das funktioniert jetzt auch so:

Delphi-Quellcode:
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;
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.


Alle Zeitangaben in WEZ +1. Es ist jetzt 21:33 Uhr.
Seite 1 von 2  1 2      

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