![]() |
Verständnisfrage zur Benutzung von Interfaces
Irgendwas hab ich falsch verstanden und es hängt wohl mit dem Referenzzähler zusammen.
Mein Testcode:
Delphi-Quellcode:
Solange ich keine der auskommentierten Zeilen aufrufe, funktioniert das Ganze. Bei SquareIt dagegen:
unit Main;
interface uses SysUtils, Forms, Dialogs, StdCtrls, Classes, Controls; type ISquare = interface(IInterface) ['{4F30FDE5-9C8C-4F34-A828-FA32B1C0DA3F}'] procedure Square; end; TInt = class(TInterfacedObject, ISquare) private FInt: Integer; public procedure Square; property Number: Integer read FInt write FInt; end; TFloat = class(TInterfacedObject, ISquare) private FFloat: Single; public procedure Square; property Number: Single read FFloat write FFloat; end; TMainForm = class(TForm) Button: TButton; procedure ButtonClick(Sender: TObject); private { Private-Deklarationen } public { Public-Deklarationen } end; var MainForm: TMainForm; implementation {$R *.dfm} procedure TInt.Square; begin FInt:= FInt * FInt; end; procedure TFloat.Square; begin FFloat:= FFloat * FFloat; end; procedure SquareIt(aNumber: ISquare); //Fehler ohne const, mit kein Fehler begin aNumber.Square; end; procedure TMainForm.ButtonClick(Sender: TObject); var aInt: TInt; aFloat: TFloat; MyInterface: ISquare; begin aInt:= TInt.Create; //MyInterface:= aInt; //Interface Referenz Counter geht hoch aFloat:= TFloat.Create; aInt.Number:= 5; //MyInterface.Square; MyInterface:= nil; //Zähler geht wieder runter aFloat.Number:= 5; aInt.Square; aFloat.Square; ShowMessage(IntToStr(aInt.Number)); ShowMessage(FloatToStr(aFloat.Number)); //SquareIt(aInt); //während der Aufrufe geht der Zähler auch hoch //SquareIt(Float); ShowMessage(IntToStr(aInt.Number)); ShowMessage(FloatToStr(aFloat.Number)); aInt.Free; aFloat.Free; end; end.
Code:
Währernd die Zuweisung auf MyInterface produziert (exakt) die selbe Meldung. Wo liegt mein Denkfehler? Wenn ich stattdessen
---------------------------
Benachrichtigung über Debugger-Exception --------------------------- Im Projekt InterfaceTest.exe ist eine Exception der Klasse EAccessViolation mit der Meldung 'Zugriffsverletzung bei Adresse 00403864 in Modul 'InterfaceTest.exe'. Lesen von Adresse FFFFFFFC' aufgetreten. --------------------------- Anhalten Fortsetzen Hilfe ---------------------------
Delphi-Quellcode:
schreibe, dann funktioniert das Ganze. Daraus folger ich mal das der Fehler etwas mit dem Kopieren einer neuen Referenz zu tun hat und das dies wohl ohne const passiert. Wie genau sollten Interfaces verwendet werden? Die Möglichkeit solcher Konstruktionen wäre ja sonst recht sinnlos?
procedure SquareIt(const aNumber: ISquare);
|
Re: Verständnisfrage zur Benutzung von Interfaces
Es gibt im Zusammenhang mit Interfaces eine wichtige Grundregel:
Arbeite niemals mit dem Objekt und den Interfacen gleichzeitig! (Wenn man genau weiß was man tut, kann man das trotzdem machen, aber es ist nicht empfehlenswert) Ich hab dir im folgenden dein Beispiel mal umgebaut. Um es nicht zu kompliziert zu machen, wird der Wert jetzt im Konstruktor übergeben und ISquare kann seine Zahl als String zurückgeben.
Delphi-Quellcode:
Das Problem in deiner Implementation war, das das Objekt freigegeben wurde, nachdem es einmal als Interface benutzt wurde. Das kommt daher das es nach dem erstellen(.Create) den RefCount 0 hat. Durch die Zuweisung an ein Interface oder die Übergabe in eine Funktion als Interface wird der RefCount auf 1 gesetzt. Am Ende der Prozedur oder durch eine Neubelegung der interfacevariable sinkt der RefCount auf 0 und für das Objekt wird .Free aufgerufen. Jede nachfolgende Verwendung wirft dann eine AV.
type
ISquare = interface(IInterface) ['{4F30FDE5-9C8C-4F34-A828-FA32B1C0DA3F}'] procedure Square; function NumberAsString : String; end; TInt = class(TInterfacedObject, ISquare) private FInt: Integer; public constructor Create(ANumber : Integer); procedure Square; property Number: Integer read FInt write FInt; function NumberAsString : String; end; TFloat = class(TInterfacedObject, ISquare) private FFloat: Single; public constructor Create(ANumber : Single); procedure Square; property Number: Single read FFloat write FFloat; function NumberAsString : String; end; TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private-Deklarationen } public { Public-Deklarationen } end; var Form1: TForm1; implementation {$R *.dfm} constructor TInt.Create(ANumber: Integer); begin FInt := ANumber; end; function TInt.NumberAsString: String; begin Result := IntToStr(FInt); end; procedure TInt.Square; begin FInt:= FInt * FInt; end; constructor TFloat.Create(ANumber: Single); begin FFloat := ANumber; end; function TFloat.NumberAsString: String; begin Result := FloatToStr(FFloat) end; procedure TFloat.Square; begin FFloat:= FFloat * FFloat; end; procedure SquareIt(aNumber: ISquare); //Fehler ohne const, mit kein Fehler begin aNumber.Square; end; procedure TForm1.Button1Click(Sender: TObject); var aInt, aFloat: ISquare; //MyInterface: ISquare; unnötig begin aInt:= TInt.Create(5); //MyInterface:= aInt; //Interface Referenz Counter geht hoch aFloat:= TFloat.Create(5); //aInt.Number:= 5; gibts nicht mehr //MyInterface.Square; //MyInterface:= nil; //Zähler geht wieder runter //aFloat.Number:= 5; gibts nicht mehr aInt.Square; aFloat.Square; ShowMessage(aInt.NumberAsString); ShowMessage(aFloat.NumberAsString); //SquareIt(aInt); //während der Aufrufe geht der Zähler auch hoch //SquareIt(Float); //ShowMessage(IntToStr(aInt.Number)); //ShowMessage(FloatToStr(aFloat.Number)); //aInt.Free; //aFloat.Free; end; |
Re: Verständnisfrage zur Benutzung von Interfaces
Am Besten du lagerst die Implementierungsklassen in eine eigene Unit aus und dort die definition nur im implementationteil und definerst sogenannte Co-Klassen die für die erzeugung zuständig sind. Damit verhinderst du das du versehentlich gemischten Interface/Klassenbetrieb fährst.
|
Re: Verständnisfrage zur Benutzung von Interfaces
Hmm, jetzt versteh ich schon mal, warum einem so ein Konstrukt um die Ohren fliegt :wink:
Bleibt noch das Problem, dass ich noch nicht so genau weiß, wie ich mein eigentliches Problem angehen soll :gruebel: Die Problemstellung geht in die Richtung, dass ich eine Objekthierachie aufbauen wollte, in der mehrerer Objekte zwei verschiedene Eigenschaften (bzw. Funktionen die sie garantieren) haben oder auch nicht und sich das dummwerweise nicht sinnvoll trennen lässt. Genauer ging es darum (nicht-/)durchlaufbare, einfach-/doppeltverkette Listen und die Funktionen von Stacks und Queues zu kombinieren, um endlich mal eine flexible Implementierung solcher Strukturen zu haben, da die einzigen Standardtypen von Delphi auf Arrays (TList) aufbauen und auch recht eingeschränkt sind. Alle meine Ideen, wie ich das mit normaler Vererbung basteln könnte, laufen leider darauf, dass ich irgendwelche unschönen Einschränkungen hinnehmen müsste, also brauch ich so eine Art unschöner Mehrfachvererbung :stupid: Als konkretes Beispiel kann hier eine ![]() Von meinem "Experiment" her, würde ich jetzt sagen, es würde funktionieren den Funktionen ein const zu verpassen, aber wenn einem sowas sonst um die Ohren fliegt, kann das ja nicht der "saubere" Weg sein (falls er überhaupt klappen würde). |
Re: Verständnisfrage zur Benutzung von Interfaces
chaosben hat eigentlich schon alles gesagt.
Wenn man Klassen-Instanzen und Interface-Referenzen gleichzeitig benutzt, ist das nicht schlecht, nur muss man ganz genau wissen was man tut ;) 1) Referenz an eine Klassen-Instanz-Variable führ nicht zu einer Erhöhung des RefCount-Werts 2) Am besten nie .Free verwenden, das Objekt sollte nur über einen RefCount = 0 freigegeben werden //edit2: noch mal deutlich: NIE .Free aufrufen wenn der RefCount > 0 ist! Denn dann wird die Instanz noch verwendet und .Free macht es ja kaputt! 3) in anderen Fällen manuell .AddRef und .ReleaseRef (oder so) aufrufen. Also am besten die gesamte Funktionalität als Interface bereitstellen und auf Klassen-Instanzen verzichten. //edit: 3) sollte dein Fall sein. Wenn du eine Klassen-Instanz persistend verwenden willst sollte einmal .AddRef nach dem Erstellen die meisten Probleme erschlagen. Aber bitte immer im Kopf behalten was mit dem Referenzzähler passiert. Dieser löst die meisten Probleme (aus)... |
Re: Verständnisfrage zur Benutzung von Interfaces
Zitat:
Delphi-Quellcode:
procedure TInterfacedObject.BeforeDestruction;
begin if RefCount <> 0 then Error(reInvalidPtr); end; |
Alle Zeitangaben in WEZ +1. Es ist jetzt 19:34 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