Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi Implementation Interface Struktur (https://www.delphipraxis.net/144266-implementation-interface-struktur.html)

slemke76 3. Dez 2009 17:55


Implementation Interface Struktur
 
Hallo zusammen,

ich habe hier eine Anwendung, die mittels Plugins Daten exportieren soll. Zu dem Thema Plugins gibt es ja sowohl hier im Board als auch im Internet eine Menge Themen.

Ich habe mich dazu entschlossen, mit einem "type IExport = interface" zu arbeiten; also _nicht_ mit einer Klasse, die weitervererbt und deren Methoden überschrieben werden.

Nun zum Problem :wink: :

Ich möchte zum einen einen CSV-Export, zum anderen einen SQL-Adapter schreiben. Beide haben natürlich eine Methode "Export", allerdings haben beide auch _unterschiedliche_ Properties - für CSV brauche ich einen Pfad, für SQL brauche ich einen Connectionstring.

Nun suche ich einen eleganten Weg, nicht alle nur denkbaren Parameter in das Interface reinzuschreiben - die dann je nach Anwendungsfall ungenutzt wären. Wie kann man das am besten machen ?

Eine Idee dazu habe ich, weiss aber nicht, ob die "elegant" ist - das ganze sähe so aus:

Interface:
Delphi-Quellcode:
type
  IExport = interface
    procedure Export(.....);
    procedure SetParameter(paramname, value: string);
  end;
Implementation:
Delphi-Quellcode:
unit T_ExportCSV;

type
  TExportCSV = class(TInterfacedObject, IExport)
  protected
    procedure Export(.........);
    procedure SetParameter(paramname, value: string);
  private
    FExportDir: string;
  public
    constructor Create;
    destructor Destroy; override;
  end;

implementation

procedure TExportCSV.Export(.........);
...

procedure TExportCSV.SetParameter(paramname, value: string);
begin
  if (paramname='FExportDir') then FExportDir:=value; // ToDo: Plausi Checks
end;
Hauptprogramm:
Delphi-Quellcode:
var
  ExportCSV: IExport; // !!! Typ IExport, nicht TExportCSV!

begin
    ExportCSV:=TExportCSV.Create;
    ExportCSV.SetParameter('FExportDir', 'C:\csvdir');
Vorweg: Ich kann NICHT ExportCSV vom Typ TExportCSV machen, das wäre zum einen ja an meiner Pluginstruktur "vorbei" zum anderen will ich später ein Array vom Typ TExport verwenden, in dem sowohl die SQL als auch die CSV-Plugins verwaltet werden sollen.

Gibts eine bessere Möglichkeit und
Kann ich in "SetParameter" das "If" vermeiden mit sowas wir Var(paramname):=value ???

lg
Sebastian

himitsu 3. Dez 2009 18:06

Re: Implementation Interface Struktur
 
neben dem Plugin gibt es eine Funktion "GibMirDasInterface"

Delphi-Quellcode:
funktion GibMirDasInterface: IExport;
begin
  Result := TExportCSV.Create;
end;
TExportCSV ist nur innerhalb des Plugins bekannt und die Anwendung kennt nur das Inteface.

sirius 3. Dez 2009 18:07

Re: Implementation Interface Struktur
 
Und Gib dem Interface eine IID! die Deklaration des Interfaces bruachen alle beteiligten Programme. Nur das Plugin implementiert dieses.

slemke76 3. Dez 2009 18:17

Re: Implementation Interface Struktur
 
Zitat:

Zitat von himitsu
neben dem Plugin gibt es eine Funktion "GibMirDasInterface"

Delphi-Quellcode:
funktion GibMirDasInterface: IExport;
begin
  Result := TExportCSV.Create;
end;

Ich hatte einen Fehler in meinem Source - natürlich ist (bei mir) ExportCSV vom Typ IExport.

Zitat:

Zitat von himitsu
TExportCSV ist nur innerhalb des Plugins bekannt und die Anwendung kennt nur das Inteface.

Aber ich verstehe deinen Post trotzdem nicht (ich glaub, ich steht auf dem Schlauch) -
Die Funktion GibMirDasInterface ist vom Typ IExport und im Typ IExport gibt es keine Möglichkeit, z.B. auf eine Property und/oder auf eine public Variable aus TExportCSV zuzugreifen (klar - hast du ja auch geschrieben).

Nur - wie mache ich das "am besten" - gibt es nichts besseres als meine "SetParameter - Durchschleif - Funktion" ?

Ich sag ja, ich steh auf dem Schlauch :gruebel:

Zitat:

Zitat von sirius
Und Gib dem Interface eine IID! die Deklaration des Interfaces bruachen alle beteiligten Programme. Nur das Plugin implementiert dieses.

Die Deklaration haben auch alle beteiligten Programme.
Die IID werde ich nachrüsten, Danke !!





Sebastian

himitsu 3. Dez 2009 18:24

Re: Implementation Interface Struktur
 
Wenn er die IID eh nicht nutzt, dann isses doch egal (?) ... Delphi legt dann einfach per Zufall irgendeine fest.


z.B.
Du hast eine DLL darin liegt das Plugin.
In der DLL und der EXE kennen beide das Interface,
aber nur die DLL bzw. das Plugin kennt das Objekt die EXE/Anwendung hat garkeinen Zugriff auf die Klasse bzw. das Objekt hinter dem Interface.

Darum auch die Funktion, damit fragt die Anwendung das Plugin nach dem gewünschten Interface
und das Plugin erstellt dann intern das Objekt und gibt nur das Interface darauf der Anwendung.

slemke76 3. Dez 2009 19:25

Re: Implementation Interface Struktur
 
Hi !

ok - jetzt weiss ich - wir haben uns missverstanden, weil ich im Source einen Fehler gemacht habe; ich habe im Hauptprogramm folgendes stehen:
Delphi-Quellcode:
    ExportCSV:=TExportCSV.Create;
    ExportCSV.SetParameter('FExportDir', 'C:\csvdir');
Was funktioniert, weil ich ein "uses" auf die T_ExportCSV.pas habe 8-)

Die Implementation von "gibmirdasinterface" ist da natürlich sauberer - nur wo und wie implementiere ich das :gruebel:
- Im Plugin (Implementierung) als "function TExportCSV.GibMirDasInterface: IExport;" kann es ja nicht funktionieren - du hast ja auch "ausserhalb" geschrieben.
- Im Plugin (Implementierung) als "function GibMirDasInterface: IExport;" brauche ich ja wieder das "uses" auf meine Plugin-Implementation - dann kann ich auch gleich den Klassennamen nehmen ? Ausserdem würde doch "Bezeichner redefiniert" bei dem zweiten geladenen Plugin kommen ?
- In das Interface ? Macht gar keinen Sinn, weil es Plugins gibt, die die Klasse TExportCSV implementieren und andere, die TExportSQL implementieren.
- handelt es sich um eine vordefinierte Systemfunktion ?

Ich glaube, das ist heute nicht mein Tag....
...oder ich habe das was grundsätzliches missverstanden.

Danke,
Sebastian

himitsu 3. Dez 2009 19:41

Re: Implementation Interface Struktur
 
wie hast du die Plugins denn aktuell?

entweder liegt das Plugin z.B. in einer DLL, dann wird "nur" diese Funktion exportiert
- oder notfalls mehrere Funktionen für weitere Interfaces
- Beide (EXE/DLL) kennen je eine "Kopie" des Interfaces
- und die sonstige Komunikation läuft über das Interface

oder du legst alles in eine Unit.
- da liegt im Interface-Abschnitt nur diese Funktion und die ganze Klasse liegt unter "Implementation"
- und dann importieren Programm und Unit einfach nur noch das selbe Interface von einer gemeinsamen Unit

sirius 3. Dez 2009 19:44

Re: Implementation Interface Struktur
 
Zitat:

Zitat von himitsu
Wenn er die IID eh nicht nutzt, dann isses doch egal (?) ... Delphi legt dann einfach per Zufall irgendeine fest.

Ich bin mir jetzt nicht sicher, ob Delphi dann eine festlegt. Aber dennoch: in DLL und Hauptprogramm sollten aber die selben IIDs verwendet werden. Und wie macht Delphi das?

himitsu 3. Dez 2009 19:50

Re: Implementation Interface Struktur
 
wenn du einmal das Interface hast, dann ist die IID soweit egal

und wird höchstens mal genutzt, wenn du das Interface vergleichen/prüfen/abfragen/casten willst.
(da wird ja die IID verglichen und nicht der Name)

sirius 3. Dez 2009 21:06

Re: Implementation Interface Struktur
 
Aber eine Zuweisung entspricht ja bei Interfaces immer einem "Cast"

himitsu 3. Dez 2009 21:15

Re: Implementation Interface Struktur
 
Zitat:

Zitat von sirius
Aber eine Zuweisung entspricht ja bei Interfaces immer einem "Cast"

Den cast macht man einmal in der GibMir-Funktion, also paßt schon.

Aber wenn er eben im Programm z.B. mit Supports prüfen will, ob es wirdklich ein "Pulgin"-Interface ist, dann braucht er schon eine einheitliche IID.

slemke76 4. Dez 2009 08:25

Re: Implementation Interface Struktur
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo zusammen,

ich habe heute Morgen die entsprechenden Routinen mal als Sample ohne "drumherum" in ein Projekt gepackt; mein Stand ist noch wie gestern Abend :pale: .

Ich möchte die "Plugins" gerne mit Units verwenden, nicht mit DLLs und weiss immer noch nicht so recht, wo das "gibmirdasinterface" reinkommt.

Wäre toll, wenn jemand einen Blick werfen würde.

lg
Sebastian

sirius 4. Dez 2009 09:03

Re: Implementation Interface Struktur
 
Bei Units brauchst du nicht zwingend eine Funktion "GibMirDasInterface". Bei Units sind anstatt Interfaces auch eher abstrakte Klassen geeignet (in Delphi).
In einer DLL brauchst du "GibMirDasInterface", weil du sonst die Klasse nicht erstellen kannst.

Den Verweis auf die beiden Units kannst du nicht verhindern (wo du "unschön" hinschriebst).


Ansonsten sieht dein Programm doch ok aus. Wo hakt es denn noch?

slemke76 4. Dez 2009 09:13

Re: Implementation Interface Struktur
 
Hi,

Zitat:

Zitat von sirius
Bei Units brauchst du nicht zwingend eine Funktion "GibMirDasInterface".
In einer DLL brauchst du "GibMirDasInterface", weil du sonst die Klasse nicht erstellen kannst.

Dann war das alles ein Missverständniss :-)

Zitat:

Zitat von sirius
Bei Units sind anstatt Interfaces auch eher abstrakte Klassen geeignet (in Delphi).

Dann müsste aber TExportCSV ein Nachkomme von TExport sein, oder ?

Zitat:

Zitat von sirius
Den Verweis auf die beiden Units kannst du nicht verhindern (wo du "unschön" hinschriebst).Ansonsten sieht dein Programm doch ok aus. Wo hakt es denn noch?

Die Ausgangsfrage war ja auch, ob das von der Struktur her "schöner" geht.
Die zweite Frage ist, ob man das "Durchschleifen" der Parameter mittels "SetParameter" besser machen kann und
die dritte wäre, ob sich das "if" Verzweigung in der procedure "SetParameter" besser lösen lässt - gibt es sowas wie "Var(paramname):=value" ?

lg
Sebastian

Blup 4. Dez 2009 11:33

Re: Implementation Interface Struktur
 
Ich könnte mir das etwa so vorstellen:
Delphi-Quellcode:
type
  IExport = interface
    procedure Export({...});
    function GetParams: TStringList;
    procedure SetParams(AValue: TStringList);
    property Params: TStringList read GetParams write SetParams;
  end;
Implementation:
Delphi-Quellcode:
procedure CreateExport: IExport; export;

implementation

type
  TExportCSV = class(TInterfacedObject, IExport)
  protected
    FParams: TStringList;
    procedure InitParams; virtual;
    procedure CheckParams; virtual;
  public {IExport}
    procedure Export({...});
    function GetParams: TStringList;
    procedure SetParams(AValue: TStringList);
  public
    constructor Create;
    destructor Destroy; override;
  end;

constructor TExportCSV.Create;
begin
  inherited;
  FParams := TStringList.Create;
  InitParams;
end;

destructor TExportCSV.Destroy;
begin
  FParams.Free;
  inherited;
end;

procedure TExportCSV.InitParams;
begin
  {alle möglichen Parameter mit default eintragen}
  with FParams do
  begin
    Add('ExportDir' + NameValueSeparator);
    Add('Separator' + NameValueSeparator + ';');
    {...}
  end;
end;

procedure TExportCSV.CheckParams;
begin
  if FParams.Values['ExportDir'] = '' then
    raise Exception.Create('Parameter ''ExportDir'' ist erforderlich.');
  if FParams.Values['Separator'] = '' then
    raise Exception.Create('Parameter ''Separator'' ist erforderlich.');
  if Length(FParams.Values['Separator']) > 1 then
    raise Exception.Create('Parameter ''Separator'' darf nur ein Zeichen enthalten.');
  {...}
end;

procedure TExportCSV.SetParams(AValue: TStringList);
begin
  FParams.Assign(AValue);
end;

function TExportCSV.GetParams: TStringList;
begin
  Result := FParams;
end;

procedure TExportCSV.Export({...});
begin
  CheckParams;
  {...}
end;

procedure CreateExport: IExport;
begin
  Result := TExportCSV.Create;
end;
Ohne das die Anwendung die Parameter der konkreten Schnittstelle kennen muss, können die Parameter so vom Anwender bearbeitet, gespeichert und gelesen werden. TStringList ist natürlich nur die einfachste Variante. Denkbar wäre eine Liste von Parameterobjekten die jeweils noch weitere Information über die Art des Paramaters besitzen und diesen validieren können.

slemke76 4. Dez 2009 13:27

Re: Implementation Interface Struktur
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hi !

Zitat:

Zitat von Blup
Ich könnte mir das etwa so vorstellen:
...

das gefällt mir richtig gut und habs dann sogleich auch implemntiert.
Aber auf eine Lösung folgt das nächste Problem.

Ich habe die Übergabe der Variablen aus dem Hauptprogramm nun so (funktioniert) :

Delphi-Quellcode:
var myParams: TStringList;
......

  myParams:=TStringList.Create;
  myParams.Add('ExportDir'+myParams.NameValueSeparator+'SAMPLE');

  SetLength(myExport, 1);
  myExport[0]:=TExportCSV.Create;
  myExport[0].Params:=myParams;
  ....

  myParams.Free;
Ich würde aber gerne so etwas wie
Delphi-Quellcode:
  myExport[0].Params.Add('Separator=;');
verwenden. Das funktioniert aber nicht ?!
Bei einem Trace geht er in TExportCSV.GetParams rein und nicht in die SetParams.

Ich habe das Projekt nochmal angehängt.

lg

Blup 7. Dez 2009 08:54

Re: Implementation Interface Struktur
 
GetParams ist schon richtig, wenn mit der Stringliste der Exportklasse gearbeitet wird.
SetParams ist eigentlich überflüssig, entspricht GetParams.Assign().
Delphi-Quellcode:
myExport[0].Params.Values['Separator'] := ';';

{oder}

with myExport[0].Params do
begin
  Values['Separator'] := ';';
  {...} 
end;
Edit:
Im Prinzip würde auch Add funktionieren, allerdings wäre der Parameter dann vieleicht doppelt vorhanden.

Elvis 7. Dez 2009 13:41

Re: Implementation Interface Struktur
 
Zitat:

Zitat von Blup
Ich könnte mir das etwa so vorstellen:
Delphi-Quellcode:
type
  IExport = interface
    procedure Export({...});
    function GetParams: TStringList;
    procedure SetParams(AValue: TStringList);
    property Params: TStringList read GetParams write SetParams;
  end;

Das geht so aber gar nicht.
TStringList ist eine Delphiklasse und würde es erzwingen dass Exe und DLL mit gleichen Runtime-Packages ausgeliefert werden.
Und wie wir das schon letztlich hatten, kann man sich dann die DLL gleich schenken. Die Zeit für dieses Pseudo-Modularisieren kann auch sinnvoller verschwenden. Mit Däumchendrehen zum Beispiel.

IMO macht würde es mehr Sinn machen, wenn das Interface eine Methode zum Setup eines Exports anbietet.

also sowas
Delphi-Quellcode:
type
  IExport = interface(IUnknown)
  ['...']
    function ExecuteModal({...}) : TModalResult; safecall;
    procedure ExecuteFromSettings({...}); safecall;

    procedure Initialize(const host : IHost); safecall;
  end;
Die jeweilige Export Implementierung könnte jetzt zum Beispiel einen Dialog zum Speichern der Datei anbieten, oder fragen in welchen Characterset der Export erfolgen soll.

Wenn das ganze automatisierbar und wiederholbar sein sollte, sollte es zusätzlich neben den reinen Benutzer-getriebenen Modus auch die Möglichketi geben, dass er die Werte ablegt.
Allerdings machtes IMO wenig Sinn das von außen durch den Host anzustoßen (die Stringlist). Der host soltle dem Plugin viel mehr einen Weg geben um Settings ablegen zu können.
Das könnte ein IXmlDocument sein. Welches vom Host erzeugt und auch wieder gespeichert wird.
Es gibt verschiedene Mittel mit denen der Host Funktionalität (nennen wir das mal einfach "Services") den PlugIns zur Verfügung stellen kann.
Das erste (und simpelste) wäre, das es direkt Methoden und Properties des IHost interfaces sind.
Delphi-Quellcode:
type IHost = interface(IUnknown)
['...']
  procedure GetSettings(out settings : IXmlDocument); safecall;
  procedure SaveSettings(const settings : IXmlDocument); safecall;
end;
Die Host-Anwendung würde für jedes Plugin solche eine IHost-Referenz anlegen, so dass ein Plugin nicht aus Versehen in den Sachen eines anderen rumschreibt.

Das ganze ist aber extrem unflexibel und führt fast zwangsläufig dazu, dass irgendwann das IHost-Interface erweitert werden muss, wodurch man entweder ein IHost2-Interface einführen müsste, oder bestehende Plugins würden nicht mehr laufen.
Das wäre schlecht!

Besser ist es die Services, die der Host (oder andere Plugins) zur verfügung stellen will auch als solche zu schreiben.

Zum Biespiel der Service, für die Settings zuständig wäre:
Delphi-Quellcode:
type
  ISettingsProvider = interface(IUnknown)
  ['...']
    procedure GetSettings(out settings : IXmlDocument); safecall;
    procedure SaveSettings(const settings : IXmlDocument); safecall;
  end;

  IHost = interface(IUnknown)
  ['...']
    function GetService(const serviceID : TGuid; out service : IUnknown) : Boolean; safecall;
    function RegisterService(const serviceID : TGuid; const service : IUnknown) : Boolean; safecall;
  end;
Jetzt würde das Plugin so seine Settings holen:
Delphi-Quellcode:
var
  settingsService : ISettingsService;
  settings       : IXmlDocument;
  tmp : IUnknown;
begin
  if host.GetService(ISettingsService, out tmp)
  and Supports(tmp, ISettingsService, settingsService) then
    settingsService.GetSettings(settings)
  else
    settings := nil;

  if assigned(settings) then
  begin
    ...
    settingsService.SaveSettings(settings);
  end;
end;
Du könntest aber die Anwendung erweitern und neue Services hinzufügen, ohne, dass dir rgendein Plugin um die Ohren fliegt, egal ob es 1 Tag oder 5 Jahre alt ist.
Sogar andere Plugins könnten Services registrieren, die alle anderen PlugIns nutzen könnten.
Plugins könnten auf die Weise in unterschiedlichen Delphiversionen oder sogar in C++ oder C# geschrieben werden.
Du könntest also in C# einen Service schreiben, der allen Plugins deiner App RegExes zur Hand gibt, oder die Crypto APIs aus .Net, etc.

slemke76 17. Jan 2011 11:11

AW: Implementation Interface Struktur
 
Hallo zusammen,

ich weiss - es ist schon ein wenig her, aber jetzt war eine Anpassung am Programm nötig und da habe ich mal weitergemacht ;-)

Nachdem ich den Thread nochmals durchgearbeitet habe, habe ich mich entschieden, die Plugins über DLLs zu implementieren - und damit war auch "gibmirdasinterface" klar.

Jetzt funktioniert alles.

Vielen Dank für die vielen Anregungen, die auch fast alle eingeflossen sind !

Grüße,
Sebastian


Alle Zeitangaben in WEZ +1. Es ist jetzt 19:44 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