Implementiert eine Komponente ein Interface?

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
braunbär
Beiträge: 369
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Implementiert eine Komponente ein Interface?

Beitrag von braunbär »

Ich habe einige eigene Komponenten entwickelt (von Standard LCL Komponenten abgeleitet), die alle das Interface IfdData implementieren sollen. Eine der Interface Methoden heisst z.B. Clear.

Jetzt würde ich gerne zur Laufzeit alle Komponenten einer Form durchgehen und bei den Komponenten, die mein Interface unterstützen, die Methode Clear aufrufen. Komponenten, die das Interface nicht unterstützen, sollen dabei einfach übergangen werden (die haben ja keine Clear Methode). Ich habe jetzt eine Weile herumprobiert, aber ich komme nicht drauf, wie man das syntaktisch richtig umsetzt.

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Implementiert eine Komponente ein Interface?

Beitrag von mse »

TObject.GetInterface() dient zu diesem Zweck:
https://www.freepascal.org/docs-html/cu ... rface.html
Das Interface muss eine GUID (bei COM-Interface) oder einen ID-string (bei Corba-Interface) haben.

braunbär
Beiträge: 369
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Implementiert eine Komponente ein Interface?

Beitrag von braunbär »

Das schaut so aus, als wäre es, was ich brauche - aber wie ich es verwenden muss, ist mir auch nach dem Lesen der Dokumentation noch immer nicht klar.

1. Wie komme ich zum Parameter GUID?
Ich deklariere das Interface beispielsweise als
IfdData = Interface ['{0AB00115-0734-0184-C000-0C5600052148}'']
(Nebenbei: Gibt es eigentlich irgend welche Richtlinien, wie man den GUID String aufbauen soll, oder macht man das völlig willkürlich? In der Delphi-IDE gibt es glaube ich sogar ein Tastaturkürzel, das einen GUID String generiert)
Woher bekomme ich die GUID Variable - oder muss ich den GUID-String zu Fuß in den GUID Typ konvertieren?

Und was übergebe ich im Parameter obj? Die Komponente selbst steht doch schon vor dem Punkt: abc.GetInterface( guid, ????); Was muss denn da hin? obj ist ein OUT Parameter.

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Implementiert eine Komponente ein Interface?

Beitrag von mse »

braunbär hat geschrieben:Das schaut so aus, als wäre es, was ich brauche - aber wie ich es verwenden muss, ist mir auch nach dem Lesen der Dokumentation noch immer nicht klar.

1. Wie komme ich zum Parameter GUID?
Ich deklariere das Interface beispielsweise als
IfdData = Interface ['{0AB00115-0734-0184-C000-0C5600052148}'']

Dann kannst du

Code: Alles auswählen

 
 if <objektinstanz>.getinterface('{0AB00115-0734-0184-C000-0C5600052148}',<interfacevariable>) then begin
  tue etwas mit dem interface
 end;
 

verwenden.
(Nebenbei: Gibt es eigentlich irgend welche Richtlinien, wie man den GUID String aufbauen soll, oder macht man das völlig willkürlich?

Diese ID soll "Globally Unique" also welt- oder universumweit einmalig sein.
In der Delphi-IDE gibt es glaube ich sogar ein Tastaturkürzel, das einen GUID String generiert)

Das gibt es in Lazarus sicher auch. In MSEide wäre es RightClick-'Insert GUID'.
Woher bekomme ich die GUID Variable - oder muss ich den GUID-String zu Fuß in den GUID Typ konvertieren?

Siehe oben. Eine andere Option ist

Code: Alles auswählen

 
 if <objektinstanz>.getinterface(stringtoguid('{0AB00115-0734-0184-C000-0C5600052148}'),<interfacevariable>) then begin
  tue etwas mit dem interface
 end;
 

EDIT: oder besser

Code: Alles auswählen

 
 if <objektinstanz>.getinterface(<interfacetyp>,<interfacevariable>) then begin
  tue etwas mit dem interface
 end;
 

welches direkt mit TGUID arbeitet.
Und was übergebe ich im Parameter obj? Die Komponente selbst steht doch schon vor dem Punkt: abc.GetInterface( guid, ????); Was muss denn da hin? obj ist ein OUT Parameter.

"obj" ist die Interface Variable.
PS: Falls du COM-Interface verwendest (die Standardeinstellung), solltest du dir überlegen, ob nicht CORBA-Interface besser geeignet wären.
Zuletzt geändert von mse am Mi 3. Okt 2018, 08:35, insgesamt 2-mal geändert.

braunbär
Beiträge: 369
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Implementiert eine Komponente ein Interface?

Beitrag von braunbär »

Danke für die Antwort, mit den Infos sollte ich weiterkommen.

Ich benötige die Interfaces in dem Framework hier eigentlich nur, um auf einer abstrakten Ebene manche Aufgaben für die Komponenten einer Form automatisch erledigen zu können, die die entsprechende Funkionalität bereitstellen. Wenn es auf der Form noch andere Komponenten gibt, dann muss sich der Programmierer der Form (zur Zeit meistens auch ich selbst) in einer Callback Routine um diese Komponenten individuell kümmern.

Keine Ahnung, ob Corba da irgend einen Vorteil bieten würde. Es geht hier explizit nicht um Schnittstellen nach außen zu anderen Programmen.

Was ich gerne wissen würde, ist noch, ob die Prüfung auf Vorhandensein des Interfaces zeitaufwändig ist - ich vermute aber eher (und hoffe), dass das nicht der Fall ist.


Edit: Nochmals danke, das hat jetzt auf Anhieb funktioniert.
Zuletzt geändert von braunbär am Di 2. Okt 2018, 21:29, insgesamt 1-mal geändert.

sstvmaster
Beiträge: 575
Registriert: Sa 22. Okt 2016, 23:12
OS, Lazarus, FPC: W10, L 2.2.6
CPU-Target: 32+64bit
Wohnort: Dresden

Re: Implementiert eine Komponente ein Interface?

Beitrag von sstvmaster »

GUID Tastenkürzel: Shift+Strg+G siehe Menü -> Quelltext -> Einfügen allgemein -> GUID einfügen

selbst eine erzeugen:

Code: Alles auswählen

var
  T : TGUID;
 
begin
  CreateGUID(T);
  ShowMessage(GUIDToString(T));
end;
LG Maik

Windows 10,
- Lazarus 2.2.6 (stable) + fpc 3.2.2 (stable)
- Lazarus 2.2.7 (fixes) + fpc 3.3.1 (main/trunk)

braunbär
Beiträge: 369
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Implementiert eine Komponente ein Interface?

Beitrag von braunbär »

Gut zu wissen.
Habs gerade ausprobiert. So schön wie meine GUID sind die aber nicht :mrgreen:

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Implementiert eine Komponente ein Interface?

Beitrag von mse »

braunbär hat geschrieben:Danke für die Antwort, mit den Infos sollte ich weiterkommen.

Ich benötige die Interfaces in dem Framework hier eigentlich nur, um auf einer abstrakten Ebene manche Aufgaben für die Komponenten einer Form automatisch erledigen zu können, die die entsprechende Funkionalität bereitstellen. Wenn es auf der Form noch andere Komponenten gibt, dann muss sich der Programmierer der Form (zur Zeit meistens auch ich selbst) in einer Callback Routine um diese Komponenten individuell kümmern.

Dann würde ich auf jeden Fall CORBA einsetzen.
Keine Ahnung, ob Corba da irgend einen Vorteil bieten würde. Es geht hier explizit nicht um Schnittstellen nach außen zu anderen Programmen.

Die üblichen COM -Interface sind referenzgezählt und vertragen sich schlecht mit aus dem Programm aufgerufenem destroy(), speziell die Kombination TComponent/COM-Interface ist ein Albtraum. CORBA-Interface haben diesen Nachteil nicht, da hat man alles unter Kontrolle; durch die fehlende Referenzzählung sind sie erst noch schneller.
Was ich gerne wissen würde, ist noch, ob die Prüfung auf Vorhandensein des Interfaces zeitaufwändig ist - ich vermute aber eher (und hoffe), dass das nicht der Fall ist.

Die Suche in den Tabellen ist aufwendig. CORBA hat den Vorteil, dass als ID ein beliebiger auch kurzer String verwendet werden kann, der lediglich in der Applikation einmalig sein muss. MSEide erzeugt solche ID's mit RightClick-'Insert UID'.
Schneller als die Tabellensuche zur Laufzeit ist Typenkonvertierung zur Kompilierzeit wenn der Klassentyp bekannt ist.

Code: Alles auswählen

 
type
{$interfaces corba}
 itest = interface ['AA']{0}  //ID not used in example.
  procedure test();
 end;
 
 ttest = class(itest)
  protected
   procedure test();
 end;
[...]
var
 t1: ttest;
[...]
 itest(t1).test();
 

Dabei wird die ID nicht benötigt. Ist die Tabellensuche unumgänglich, empfehlen sich ID-Konstanten in der Form nummer.framework.

Code: Alles auswählen

 
const
 id_test = 'AA.braunbaer';{0}
type
{$interfaces corba}
 itest = interface [id_test]
  procedure test();
 end;
 

In MSEgui liegen alle ID's zentral in der Datei lib/common/kernel/mseinterfaces.pas.
Die Tabellenabfrage ist auch in der Form

Code: Alles auswählen

 
var
 t1: ttest;
 intf1: itest;
[...]
 if t1.getinterface(itest,intf1) then begin
  intf1.test();
 end;
 

möglich.

braunbär
Beiträge: 369
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Implementiert eine Komponente ein Interface?

Beitrag von braunbär »

Momentan funktioniert es mit den standard COM Interfaces. Ich werde aber Corba auch noch ausprobieren. Wenn ich es richtig verstehe, muss ich nur in allen Units, die so ein Interface verwenden, {$interfaces corba} vor die erste Verwendung/Deklaration einfügen, und dann kann ich, wenn ich will, statt der GUID auch einen kürzeren Namen verwenden.

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Implementiert eine Komponente ein Interface?

Beitrag von mse »

Ja, und es gibt keine Probleme mit Referenzzählung.

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

Re: Implementiert eine Komponente ein Interface?

Beitrag von Warf »

So ne ganz dumme frage, warum verwendet man nicht einfach den is operator?

Code: Alles auswählen

if MyObj is ISomeInterface then
  (MyObj as ISomeInterface).SomeInterfaceMethod;


Das macht intern zwar auch den kram mit den GUIDs aber als entwickler muss man sich nicht um die guids kümmern. Ledeglich wenn man ein Interface schreibt einmal in Lazarus STRG+SHIFT+G drücken um die GUID zu generieren, danach kann man die GUID direkt wieder vergessen.

Der as Operator würde im notfall auch eine Exception werfen wenn die Klasse das Interface nicht implementiert. Daher kann man auch einfach mit einem try except arbeiten

Code: Alles auswählen

try
  ifVar := SomeObj as ISomeInterface
except
  on E: Exception do
    // kein ISomeInterface typ
end;

braunbär
Beiträge: 369
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Implementiert eine Komponente ein Interface?

Beitrag von braunbär »

Versteh ich auch nicht. Es wäre die intuitiv naheliegendste Variante, und es war eigentlich das erste Konstrukt, das ich versucht habe - erst nachdem der Compiler das nicht wollte, habe ich angefangen, die Doku durchzustöbern, und dann den Thread hier eröffnet.

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

Re: Implementiert eine Komponente ein Interface?

Beitrag von Warf »

braunbär hat geschrieben:Versteh ich auch nicht. Es wäre die intuitiv naheliegendste Variante, und es war eigentlich das erste Konstrukt, das ich versucht habe - erst nachdem der Compiler das nicht wollte, habe ich angefangen, die Doku durchzustöbern, und dann den Thread hier eröffnet.


Du musst eine guid erstellen (Strg+Shift+g in laz) damit müsste es funktionieren

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Implementiert eine Komponente ein Interface?

Beitrag von mse »

Warf hat geschrieben:So ne ganz dumme frage, warum verwendet man nicht einfach den is operator?

Performance?

Code: Alles auswählen

if MyObj is ISomeInterface then
  (MyObj as ISomeInterface).SomeInterfaceMethod;


Das macht intern zwar auch den kram mit den GUIDs aber als entwickler muss man sich nicht um die guids kümmern. Ledeglich wenn man ein Interface schreibt einmal in Lazarus STRG+SHIFT+G drücken um die GUID zu generieren, danach kann man die GUID direkt wieder vergessen.

Das ist hier auch nicht anders:

Code: Alles auswählen

 
 if t1.getinterface(itest,intf1) then begin
  intf1.test();
 end;
 

und die Tabellenabfrage geschieht nur einmal. Wenn man CORBA statt COM-Interfaces einsetzt gibt es zusätzlich keinen Overhead durch die Referenzzählung.
Der as Operator würde im notfall auch eine Exception werfen wenn die Klasse das Interface nicht implementiert. Daher kann man auch einfach mit einem try except arbeiten

Code: Alles auswählen

try
  ifVar := SomeObj as ISomeInterface
except
  on E: Exception do
    // kein ISomeInterface typ
end;

Hier ist die Performance noch schlechter. Verfolge mal mit dem Debugger was so alles passiert. Vor allem das Auslösen einer exception ist sehr aufwendig aber auch das einfache try/except ist nicht kostenlos. In MSElang verwende ich "Zero-cost exception handling" aber auch dort ist nur der exceptionlose Durchlauf durch die Exception-Strukturen ohne Zusatzaufwand und die EXE wird durch die abgelegten Verwaltungsinformationen grösser.

MitjaStachowiak
Lazarusforum e. V.
Beiträge: 394
Registriert: Sa 15. Mai 2010, 13:46
CPU-Target: 64 bit
Kontaktdaten:

Re: Implementiert eine Komponente ein Interface?

Beitrag von MitjaStachowiak »

So ne ganz dumme frage, warum verwendet man nicht einfach den is operator?


Hallo, hier ein Beispiel, was passiert, wenn man keine IDs für die Interfaces festgelegt hat:

Code: Alles auswählen

program interfaceproblem;
 
{$mode objfpc}{$H+}
 
uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes, sysutils;
 
type
  {$interfaces corba}
  IInt1 = interface
    procedure test1;
  end;
 
  IInt2 = interface
    procedure test2;
  end;
 
  TObj1 = class(IInt1)
    procedure test1;
  end;
 
  TObj2 = class(TObj1, IInt2)
    procedure test2;
  end;
 
procedure TObj1.test1;
begin
  writeln('test 1 called');
end;
 
procedure TObj2.test2;
begin
  writeln('test 2 called');
end;
 
procedure test;
var
  int : IInt1;
begin
  writeln('start test');
  int := TObj2.create as IInt1;
  int.test1// ERROR: prints test 2 called
end;
 
begin
  test;
end.
 


Ich hatte jüngst ein schwer auffindbares Bug in einem Programm mit diversen Interfaces. Die Is und As-Operatoren funktionierten zwar, aber der Aufruf der Interface-Methoden ging nicht so richtig. Dann habe ich entdeckt, dass ich die GUID vergessen hatte :roll:

Hier im Beispiel überschreibt die Implementierung von IInt2 offenbar irgendwie die Memory Tabelle von IInt1. Das geht auch, wenn die Methoden vollkommen verschiedene Parameter verwenden und dann sucht man den Fehler :mrgreen:

Wieso kompiliert der Code im Beispiel überhaupt ohne Fehlermeldung?

Antworten