Verständnisfrage zu Interfaces

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1746
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Verständnisfrage zu Interfaces

Beitrag von fliegermichl »

Ich hatte in der Vergangenheit noch nicht allzu viel mit Interfaces zu tun. Jetzt ergab sich jedoch eine Notwendigkeit.
Ich möchte in einem Programm den TVirtualStringTree verwenden und darin auch einen sog. Inplace Editor.

Wenn man in den Quellcode von Laz.VirtualTrees schaut, stellt man fest, daß diese mit Hilfe von Interfaces (IVTEditLink) implementiert werden können.
Eine spezielle TStringEditLink Implementierung ist dort ebenfalls zu finden und funktioniert auch tadellos.

Nun hatte ich aber den Anspruch, keinen einfachen Stringeditor, sondern eine Combobox "inplace" zu basteln.
Wenn man Tante Google fragt, stößt man u.a. auf dieses Beispiel.

Da werden in der Methode PrepareEdit hart codiert, einige Items hinzugefügt.
Das hat mir nicht gefallen und ich wollte einen Event zur Verfügung stellen, der eine von der Anwendung bereitgestellte Methode zum befüllen der Items verwendet und innerhalb von PrepareEdit aufgerufen wird.

Dabei musste ich feststellen, daß das auch einwandfrei compiliert, aber nicht funktioniert, weil der zuvor zugewiesene Event auf einmal nil ist!

Um das näher zu beleuchten, habe ich eine kleine Testanwendung geschrieben und dabei festgestellt, daß die Ursache der Zugriff auf die Instanz der Editorklasse über die Schnittstellenvariable ist. Das klingt kompliziert, aber wird im anhängenden Beispiel klarer.
Die Klasse TCombobanane ist eine Implementation des Interfaces IBanane und definiert zusätzlich den Event OnFillItems, welcher nach dem erzeugen zugewiesen und in Prepare aufgerufen wird.

Die Anwendung hat zwei Buttons. Bei dem einen wird OnFillItems direkt über die Interfacevariable ifObject aufgerufen (Was schonmal eine Compilerwarnung verursacht (ifomain.pas(88,5) Warning: Class types "IBanane" and "TComboBanane" are not related) - wieso?)
Bei dem zweiten über einen Zwischenschritt, in dem die Instanz über eine Methode GetObj ermittelt wird, die einfach nur self zurückliefert.

Für mein Verständnis sollte das Verhalten in beiden Fällen eigentlich gleich sein.

Code: Alles auswählen

procedure TForm1.btnCreateComboClick(Sender: TObject);
var obj : TObject;
begin
  ifObject := TComboBanane.Create;
  if (Sender = btnCreateComboIface) then
    TComboBanane(ifObject).OnFillItems := @SetItems // <- wird mit Warnung compiliert, funktioniert aber nicht. fOnFillItems bleibt nil
  else begin
    obj := ifObject.GetObject;
    TComboBanane(Obj).OnFillItems := @SetItems; // <- funktioniert
  end;
  ifObject.PrepareBanane(Sender as TWinControl);
  ifObject.UseBanane;
end;
Wieso ist die Implementation des Interfaces nicht related mit dem Interface selbst? Wenn nicht die, wer dann?
Dateianhänge
ifo.zip
Beispiel Interfaced Object
(95.16 KiB) 50-mal heruntergeladen

Socke
Lazarusforum e. V.
Beiträge: 3181
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Verständnisfrage zu Interfaces

Beitrag von Socke »

fliegermichl hat geschrieben: Mi 11. Feb 2026, 07:41 Wieso ist die Implementation des Interfaces nicht related mit dem Interface selbst? Wenn nicht die, wer dann?
Ein Interface ist eher mit einer Liste von Methoden zu vergleichen. Daher hast du dort keine Informationen mehr über die implementierende Klasse. Lediglich die Vererbung in der Interface-Hierarchie bleibt erhalten. Ein Typecast von einem Interface auf eine Klasse ist damit nicht möglich.

FreePascal unterstützt auch Properties in Interfaces. Wenn du das mit Getter- und Setter-Methoden. Versuch mal Folgendes:

Code: Alles auswählen

type
  IBanane = interface
    // alle anderen Methoden
    function GetOnFillItems: TFillItems;
    procedure SetOnFillItems(aValue: TFillItems);
    property  OnFillItems : TFillItems read GetOnFillItems write SetOnFillItems;
  end;
  
  TComboBanane = class(TInterfacedObject, IBanane)   
    fOnFillItems : TFillItems;
  public
   // alle anderen Methoden
   // Die Property OnFillItems muss nicht erneut definiert werden, damit sie im Interface sichtbar ist
   function GetOnFillItems: TFillItems;
   procedure SetOnFillItems(aValue: TFillItems);
  end;
 
implementation

procedure TComboBanane.SetOnFillItems(aValue: TFillItems);
begin
  fOnFillItems := aValue;
end;

function TComboBanane.GetOnFillItems: TFillItems;
begin
  Result := fOnFillItems;
end;
Damit hast du die Property sauber im Interface und kannst darüber auf das Event zugreifen.

Code: Alles auswählen

procedure TForm1.btnCreateComboClick(Sender: TObject);
begin
  ifObject := TComboBanane.Create;
  ifObject.OnFillItems := @SetItems;
  ifObject.PrepareBanane(Sender as TWinControl);
  ifObject.UseBanane;
end;
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Benutzeravatar
Zvoni
Beiträge: 544
Registriert: Fr 5. Jul 2024, 08:26
OS, Lazarus, FPC: Windoof 10 Pro (Laz/FPC fixes)
CPU-Target: 64Bit
Wohnort: BW

Re: Verständnisfrage zu Interfaces

Beitrag von Zvoni »

Vom Wiki: https://wiki.freepascal.org/Interfaces
Interface isn't object! It's just way to access object. I.e., well, interface. So it's name is self-explanatory. And it should be obvious, that object can have several ways to access it, i.e. several interfaces. Think about them as about "sockets" to plug other objects to your object.
Vermutung: Die GetObject-Variante funktioniert, weil du in diesem Fall das Interface-"Self" explizit als ein TObject zurückgibst.
dein Event hat ".... of Object" --> ifObject ist kein Object
Aber wie gesagt: Nur eine Vermutung.

btw: Warum setzt du dein Event OnFillItems nicht ins Interface?
Properties gehen in Interfaces

Code: Alles auswählen

TMyInterface = interface
      function GetX:TData;
      procedure SetX(AX:TData);
      property X:TData read GetX write SetX;//Properties are allowed here!!! It's way to make interfaces to look more like objects.
    end;
EDIT: Sehe gerade dass Socke mehr oder weniger dasselbe schreibt
Ein System sie alle zu knechten, ein Code sie alle zu finden,
Eine IDE sie ins Dunkel zu treiben, und an das Framework ewig zu binden,
Im Lande Redmond, wo die Windows drohn.

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 7141
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: Verständnisfrage zu Interfaces

Beitrag von af0815 »

Kurzerklärung: Ein Interface ist nichts anderes wie eine Vereinbarung, das man die definierte Schnittstelle erfüllen muss. Mehr nicht. Wenn du ein Interface aufnimmst in deine Typendeklaration, so muss du die im Interface vereinbarten Schnittstellen komplett erfüllen.

Umgekehrt, wenn di in deinem Code abfragst, ob das Objekt die Schnittstelle hat, so kannst du darauf vertrauen, das du das Objekt über die Schnittstelle ansprechen kannst, auch wenn die anderen Teile des Objektes unbekannt sind.

Bei WIndows funktioniert zum Beispiel DirectShow nur über die Interfaces. Damit kannst du Funktionen aus fremden Code verwenden, der nur über das Interface bekannt ist.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1746
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: Verständnisfrage zu Interfaces

Beitrag von fliegermichl »

Socke hat geschrieben: Mi 11. Feb 2026, 08:53
fliegermichl hat geschrieben: Mi 11. Feb 2026, 07:41 Wieso ist die Implementation des Interfaces nicht related mit dem Interface selbst? Wenn nicht die, wer dann?
Ein Interface ist eher mit einer Liste von Methoden zu vergleichen. Daher hast du dort keine Informationen mehr über die implementierende Klasse. Lediglich die Vererbung in der Interface-Hierarchie bleibt erhalten. Ein Typecast von einem Interface auf eine Klasse ist damit nicht möglich.

FreePascal unterstützt auch Properties in Interfaces. Wenn du das mit Getter- und Setter-Methoden. Versuch mal Folgendes:
...
Danke für die Erklärung. Das ist natürlich die sauberste Lösung. Auf die Idee bin ich gar nicht gekommen, weil ich wusste, daß Interfaces keine Felder haben können.
Das man jedoch schonmal Getter- und Settermethoden sowie dazu passende Properties definieren kann, war mir neu.

Benutzeravatar
theo
Beiträge: 11166
Registriert: Mo 11. Sep 2006, 19:01

Re: Verständnisfrage zu Interfaces

Beitrag von theo »

Nur so nebenbei: Im Zusammenhang mit Interfaces ist vllt. auch die "Managed types and reference counts" Seite recht interessant:
https://www.freepascal.org/docs-html/ref/refsu71.html

Warf
Beiträge: 2266
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Verständnisfrage zu Interfaces

Beitrag von Warf »

Wenn du zwischen Objekten und Interfaces castest musst du den "as" operator oder eine der Methoden benutzen.
Der Grund dafür ist das im Gegensatz zu klassischer Vererbung das Objekt (also die Speicheradresse) nicht identisch ist, sondern jedes interface seine eigene VMT hat zwischen denen hin und her gerechnet werden muss

Code: Alles auswählen

TMyClass(iface); // falsch
iface as TMyClass; // richtig

Benutzeravatar
Zvoni
Beiträge: 544
Registriert: Fr 5. Jul 2024, 08:26
OS, Lazarus, FPC: Windoof 10 Pro (Laz/FPC fixes)
CPU-Target: 64Bit
Wohnort: BW

Re: Verständnisfrage zu Interfaces

Beitrag von Zvoni »

theo hat geschrieben: Mi 11. Feb 2026, 10:52 Nur so nebenbei: Im Zusammenhang mit Interfaces ist vllt. auch die "Managed types and reference counts" Seite recht interessant:
https://www.freepascal.org/docs-html/ref/refsu71.html
Ich erinner mich daran, dass ich mit dem RefCounting von COM Kopfschmerzen hatte
{$Interfaces Corba} war dann die Lösung.
Musst dich dann aber auch um jeden Kram selbst kümmern.
https://wiki.freepascal.org/How_To_Use_Interfaces

Kurzfassung:
Nutzt du Corba, dann musst du "Free"-en was du "Create"-d hast (wie man es sonst auch gewohnt ist)
Nutzt du den Default (COM), musst du um die Ecke denken, eben nicht zu "Free"-en....
Ein System sie alle zu knechten, ein Code sie alle zu finden,
Eine IDE sie ins Dunkel zu treiben, und an das Framework ewig zu binden,
Im Lande Redmond, wo die Windows drohn.

Benutzeravatar
theo
Beiträge: 11166
Registriert: Mo 11. Sep 2006, 19:01

Re: Verständnisfrage zu Interfaces

Beitrag von theo »

Genau. Oder halt einfach die Tatsache, dass

Code: Alles auswählen

var  y: ITest;  
begin  
  y := TTest.Create;  
und:

Code: Alles auswählen

var  y : TTest;  
begin  
  y := TTest.Create; 
...(mindestens bei COM) nicht das Gleiche ist, kann einen schon verwirren und es ist leicht zu übersehen.

Benutzeravatar
Zvoni
Beiträge: 544
Registriert: Fr 5. Jul 2024, 08:26
OS, Lazarus, FPC: Windoof 10 Pro (Laz/FPC fixes)
CPU-Target: 64Bit
Wohnort: BW

Re: Verständnisfrage zu Interfaces

Beitrag von Zvoni »

Mann kann dann aber auch viele lustige Sachen machen

Code: Alles auswählen

program Project1;
{$mode objfpc}{$H+}
Uses Classes, Sysutils;
Type
  IPerson = Interface
  ['{B079F2A1-B8D8-471A-B938-6D20EA5243CB}']
    Procedure SetName(Const AName:String);
    Function GetName:String;
    Procedure GrussVomKlo;
    Property Name:String Read GetName Write SetName;
  end;
  IMann = Interface(IPerson)
  ['{65C65FC9-DB48-44DB-AFB7-11AC0093DDF0}']
    Procedure IchBinMann;
  End;
  IFrau = Interface(IPerson)
  ['{5D0EF740-BD98-4F6F-BFD5-97D99A6D58A1}']
    Procedure IchBinFrau;
  End;
  TVater=Class(TInterfacedObject, IMann)
    Private
      FName:String;
    Protected
      Procedure SetName(Const AName:String);
      Function GetName:String;
    Public
      Procedure GrussVomKlo;
      Procedure IchBinMann;
      Property Name:String Read GetName Write SetName;
      Constructor Create(Const AName:String);
  end;
  TMutter = Class(TInterfacedObject, IFrau)
    Private
      FName:String;
    Protected
      Procedure SetName(Const AName:String);
      Function GetName:String;
    Public
      Procedure GrussVomKlo;
      Procedure IchBinFrau;
      Property Name:String Read GetName Write SetName;
      Constructor Create(Const AName:String);
  end;

Var
  Vater, Mutter:IPerson;

procedure TMutter.SetName(const AName: String);
begin
  FName:=AName;
end;
function TMutter.GetName: String;
begin
  Result:=FName;
end;
procedure TMutter.GrussVomKlo;
begin
  Writeln(Name+' sagt: Hello from the Scheisshaus');
end;
procedure TMutter.IchBinFrau;
begin
  Writeln(Name+' schreit: Nicht vor den Kindern!');
end;
constructor TMutter.Create(const AName: String);
begin
  Inherited Create;
  Name:=AName;
end;
procedure TVater.SetName(const AName: String);
begin
  FName:=AName;
end;
function TVater.GetName: String;
begin
  Result:=FName;
end;
procedure TVater.GrussVomKlo;
begin
  Writeln(Name+' sagt: Hello from the Scheisshaus');
end;
procedure TVater.IchBinMann;
begin
  Writeln(Name+' schreit: GRUZEFIX!');
end;
constructor TVater.Create(const AName: String);
begin
  Inherited Create;
  Name:=AName;
end;
Procedure testIt(APerson:IPerson);
Begin
  If APerson Is IMann Then
     (APerson As IMann).IchBinMann;
  If APerson Is IFrau Then
     (APerson As IFrau).IchBinFrau;
  APerson.GrussVomKlo;
End;

begin
  Vater:=TVater.Create('Papa') As IMann;
  Mutter:=TMutter.Create('Mama') As IFrau;
  TestIt(Vater);
  testIt(Mutter);
  Readln;
  //Wir nutzen COM-Interface --> Kein Free!
end.
Ein System sie alle zu knechten, ein Code sie alle zu finden,
Eine IDE sie ins Dunkel zu treiben, und an das Framework ewig zu binden,
Im Lande Redmond, wo die Windows drohn.

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 7141
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: Verständnisfrage zu Interfaces

Beitrag von af0815 »

Zvoni hat geschrieben: Mi 11. Feb 2026, 11:27 Ich erinner mich daran, dass ich mit dem RefCounting von COM Kopfschmerzen hatte
{$Interfaces Corba} war dann die Lösung.
Martin Schreiber hat mir hierzu vor Jahren eine Antwort gegeben. Für die Lösung musste man über Pointer das Objekt free'n, weil ansonsten der RefCounter falsch gestanden wäre. Da ging es bei mir um einen fix in Lazarus und da hat es geknallt. Puh, das ist lange her. Aber wenn man das weis, kann man auch mit den RefCounter besser umgehen. Mal sehen ob ich da noch einen Link finde.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Warf
Beiträge: 2266
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Verständnisfrage zu Interfaces

Beitrag von Warf »

Bei COM interfaces muss man entweder ausschließlich mit den COM interfaces arbeiten, dann ist referenzzählung trivial, oder man muss immer wenn man mit dem Objekt als klasse interagiert die Referenzzählung manuell triggern. Also nach dem create ein _AddRef und statt dem free dann _Release aufrufen.

Manchmal mach ichs mir einfach und initialisiere die refcount im Konstruktor einfach mit 1, dann spare ich mir das AddRef

Antworten