AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Sprachen und Entwicklungsumgebungen Object-Pascal / Delphi-Language Delphi Verständnisfrage zur Benutzung von Interfaces
Thema durchsuchen
Ansicht
Themen-Optionen

Verständnisfrage zur Benutzung von Interfaces

Ein Thema von Cyf · begonnen am 24. Jul 2009 · letzter Beitrag vom 25. Jul 2009
Antwort Antwort
Cyf

Registriert seit: 30. Mai 2008
407 Beiträge
 
Lazarus
 
#1

Verständnisfrage zur Benutzung von Interfaces

  Alt 24. Jul 2009, 01:55
Irgendwas hab ich falsch verstanden und es hängt wohl mit dem Referenzzähler zusammen.
Mein Testcode:

Delphi-Quellcode:
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.
Solange ich keine der auskommentierten Zeilen aufrufe, funktioniert das Ganze. Bei SquareIt dagegen:

Code:
---------------------------
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  
---------------------------
Währernd die Zuweisung auf MyInterface produziert (exakt) die selbe Meldung. Wo liegt mein Denkfehler? Wenn ich stattdessen procedure SquareIt(const aNumber: ISquare); 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?
Man kann einen Barbier definieren als einen, der alle diejenigen rasiert, und nur diejenigen, die sich nicht selbst rasieren.
Rasiert sich der Barbier?
  Mit Zitat antworten Zitat
Benutzerbild von chaosben
chaosben

Registriert seit: 27. Apr 2005
Ort: Görlitz
1.358 Beiträge
 
Delphi XE2 Professional
 
#2

Re: Verständnisfrage zur Benutzung von Interfaces

  Alt 24. Jul 2009, 06:12
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:
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;
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.
Benjamin Schwarze
If I have seen further it is by standing on the shoulders of Giants. (Isaac Newton)
  Mit Zitat antworten Zitat
Benutzerbild von Bernhard Geyer
Bernhard Geyer

Registriert seit: 13. Aug 2002
17.201 Beiträge
 
Delphi 10.4 Sydney
 
#3

Re: Verständnisfrage zur Benutzung von Interfaces

  Alt 24. Jul 2009, 07:03
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.
Windows Vista - Eine neue Erfahrung in Fehlern.
  Mit Zitat antworten Zitat
Cyf

Registriert seit: 30. Mai 2008
407 Beiträge
 
Lazarus
 
#4

Re: Verständnisfrage zur Benutzung von Interfaces

  Alt 24. Jul 2009, 23:50
Hmm, jetzt versteh ich schon mal, warum einem so ein Konstrukt um die Ohren fliegt
Bleibt noch das Problem, dass ich noch nicht so genau weiß, wie ich mein eigentliches Problem angehen soll
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
Als konkretes Beispiel kann hier eine Deque herhalten, bei der sich nun die Frage stellt, ist das eine doppeltverkettete Liste, ein Stack oder eine Queue. Also wollte ich (da es ja auch Stacks und Queues gibt, die das jeweils andere nicht können und man eine doppelt verkettete Liste auch nochmals durchlaufbar machen kann) der Deque jetzt am liebsten ein IStack und ein IQueue verpassen und Funktionen nutzen, die alles entgegennehmen und nutzen können, das diese Interfaces unterstützt? Wie geht man das jetzt am besten an? Man soll die Objekte ja auch schließelich außerhalb der Funktionen normal benutzen können und nicht immer nur auf einen Teil ihrer Funktion zugreifen können.
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).
Man kann einen Barbier definieren als einen, der alle diejenigen rasiert, und nur diejenigen, die sich nicht selbst rasieren.
Rasiert sich der Barbier?
  Mit Zitat antworten Zitat
thabaker

Registriert seit: 1. Jul 2007
50 Beiträge
 
Turbo Delphi für Win32
 
#5

Re: Verständnisfrage zur Benutzung von Interfaces

  Alt 25. Jul 2009, 01:13
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)...
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#6

Re: Verständnisfrage zur Benutzung von Interfaces

  Alt 25. Jul 2009, 09:03
Zitat von thabaker:
//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!
das nicht gerade, aber es kommt zu einer netten Nebenwirkung, wenn man Free im falschen Moment aufruft.
Delphi-Quellcode:
procedure TInterfacedObject.BeforeDestruction;
begin
  if RefCount <> 0 then
    Error(reInvalidPtr);
end;
$2B or not $2B
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 11:19 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