Autor Beitrag
jackle32
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 183
Erhaltene Danke: 7

Win7
Delphi XE5 Starter, RAD Studio XE7 Pro
BeitragVerfasst: Sa 30.05.15 12:57 
Hallo zusammen,

bin gerade dabei eine Anwendung mit Embedded DB zu schreiben. Dabei verwende ich die FireDAC Komponenten um auf eine Access Datei zuzugreifen.

Meine Idee ist jetzt den Server (oder die Connection wie es bei FireDAC heißt) im Hauptprogramm laufen zu lassen und die entsprechend nötigen Tabellen in die einzelnen Dll´s auszulagern. Somit wäre ja mein Hauptprogram möglichst schlank und ich kann durch beliebige Dll´s die Funktionalität frei vom Hauptprogramm erweitern.

Dafür habe ich mir eine Procedure geschrieben, mit der die Connection in meiner Dll bekannt gemacht wird. Sieht so aus:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
//Teil im Hauptprogramm:

procedure TFormVerwaltung.Button4Click(Sender: TObject);
begin
  @SetConnection := self.ElementVerwaltung.SetConnection;
  if assigned(SetConnection) then
    SetConnection(Form3.FDConnection1)
  else
    ShowMessage('Dll nicht geladen');
end;

//Teil in meiner Dll:

procedure SetConnection(Server: TFDConnection); stdcall;
begin
  ControllerVerwaltung.SetzenVerbindung(Server);
end;

//Teil in meinem Zwischenkontroller:

function TControllerVerwaltung.SetzenVerbindung(Verbindung: TFDConnection): boolean;
begin
  FrmVerwaltung.Verbindung := Verbindung;
  result := true;
end;


Das ganze scheint auch zu funktionieren, da ich nach dem Setzen die Eigenschaft Connection meiner Tabelle sauber auslesen kann.

Jetzt wird es mysteriös:

Wenn ich jetzt das hier ausführe:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
procedure TFrmVerwaltung.Button2Click(Sender: TObject);
begin
  self.meineTabelle.Connection := self.Verbindung;
  self.Edit1.Text := self.meineTabelle.Connection.Name;
  self.meineTabelle.TableName := 'ButtonTest';
  self.meineTabelle.Active := true;
end;

//oder auch

procedure TFrmVerwaltung.CheckBox1Click(Sender: TObject);
begin
  if self.CheckBox1.Checked then
    self.meineTabelle.Open('ButtonTest')
  else
    self.meineTabelle.Close;
end;


bekomme ich beim aktivieren immer die Fehlermeldung: "Ungültige Typumwandlung". Mein Debugger sagt mir noch die kommt aus der Klasse EInvalidCast.

Wenn ich das gleich im meinem Hauptprogamm mit einer Tabelle mache, mit exakt dem selben Code funktioniert alles wunderbar.

Jemand eine Idee was ich noch anders machen muss, wenn ich die Tabelle in meine DLL auslagere???

Gruß,
Jack

_________________
Es gibt keine dummen Fragen, nur dumme Antworten.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Sa 30.05.15 15:54 
Du kannst keine Objekte an DLLs übergeben. Das mag zwar zufällig mal funktionieren, aber Anwendung und DLL haben eigene Versionen aller Klassen. Deshalb funktioniert es bei unterschiedlichen Delphiversionen nicht und Casts mit is und as funktionieren auch nicht. FireDAC benutzt letztere recht häufig, so dass solche Objekte definitiv nicht gehen.

Lösen kannst du das entweder mit Packages (würde ich aber nicht machen, Stichwort BPL-Hölle...) oder sauber mit Interfaces. Mit Interfaces haben wir das auch gelöst. Es gibt in der DLL eine automatisch initialisiert Schnittstelle, über die man sich die Interfaces holen kann.

Sprich du erstellst Interfaces um TDataSet und alles was du sonst brauchst. Bei uns sieht das dann so aus:
ausblenden Delphi-Quelltext
1:
TApplicationInterface.Get<IDatasetGateway>('select * from xyz').Open;					
In IDatasetGateway sind dann wie schon geschrieben die Methoden von TDataset verfügbar.

Du kannst auch einfach die Interfaces als DLL-Funktionsparameter übergeben.
jackle32 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 183
Erhaltene Danke: 7

Win7
Delphi XE5 Starter, RAD Studio XE7 Pro
BeitragVerfasst: Sa 30.05.15 20:59 
Hallo jaenicke,

danke für deine Antwort.

Scheinbar habe ich dann so einen Fall mit zufällig, da die meisten Funktionen auf der Connection funktionieren (z.B. auch der Einstellungendialog).

Was du als Lösung vorgeschlagen hast, hab ich leider gar nicht verstanden. Ich habe auch noch nie mit spitzen Klammern gearbeitet. Was bedeuten die?

Was ich auch nicht verstehe (vielleicht weil ich die ganze Lösung noch nicht überissen habe), wie mir ein Interface hilft. Die Idee die ich hatte, war ja eine direkte Verbindung auf meinen Embedded Server zu bekommen und damit direkt eine Tabelle in der DLL füllen. Somit hätte ich ja in meiner DLL sofort den vollen Datenzugriff ohne mich um den Server als solches zu kümmern und mein Hauptprogramm hat die volle Kontrolle wo die Daten her kommen. Oder ist so was falsch gedacht?
Produziere ich mit dem Datenbankaufruf über ein Interface nicht auch jede Menge Traffic auf dem Interface, um den ich mich dann selber kümmern muss?

Gruß,
Jack

_________________
Es gibt keine dummen Fragen, nur dumme Antworten.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Sa 30.05.15 23:19 
Die spitzen Klammern sind Generics. Ein sehr nützliches Feature, aber das würde hier zu weit führen...

Um das so zu lösen wie du geschrieben hast, erstellst du ein Interface IDatabaseGateway, mit dem du dir die konkreten Tabellen als Interface holst:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
type
  IDatasetGateway = interface
  ['{697364DC-C7BF-41DC-BA30-465BD82A38BF}']
    procedure Open;
  end;

  TDatasetGateway = class(TInterfacedObject, IDatasetGateway)
  private
    FDataset: TDataset;
  public
    constructor Create(const ADataset: TDataset);
    procedure Open;
  end;

  IDatabaseGateway = interface
  ['{6061AB79-96A0-47CF-91EC-0D2692F9193E}']
    function OpenQuery(const AQueryText: WideString): IDatasetGateway;
  end;

  TDatabaseGateway = class(TInterfacedObject, IDatabaseGateway)
  public
    function OpenQuery(const AQueryText: WideString): IDatasetGateway;
  end;

//...

{ TDatabaseGateway }

function TDatabaseGateway.OpenQuery(const AQueryText: WideString): IDatasetGateway;
var
  ResultDataset: TDataset;
begin
  ResultDataset := // hier das Dataset initialisieren und den Querytext einsetzen
  Result := TDatasetGateway.Create(ResultDataset);
end;

{ TDatasetGateway }

constructor TDatasetGateway.Create(const ADataset: TDataset);
begin
  inherited Create;
  FDataset := ADataset;
end;

procedure TDatasetGateway.Open;
begin
  FDataset.Open;
end;
Du musst dann alle Methoden in dein DatasetGateway einbauen, die du benötigst, und auf das originale Dataset durchleiten. Das ist schon alles.
An die DLL übergibst du dann einfach ein IDatabaseGateway und in der DLL kannst du über OpenQuery die Datasets holen.
jackle32 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 183
Erhaltene Danke: 7

Win7
Delphi XE5 Starter, RAD Studio XE7 Pro
BeitragVerfasst: So 31.05.15 10:28 
Danke für die Antwort.

Den Source Code muss ich mir auf jeden Fall nochmal genau ansehen. Wie ich es aber auf die Schnelle verstanden hab, werden ja die Daten auf der Daten Ebene hin und her geschoben.
Um das zu können, müssen aber ja alle benötigten Tabellen im Hauptprogramm angelegt und bekannt sein. Richtig?

Falls das so ist, ist es ja genau das was ich nicht will. Idee soll ja sein, dass jede DLL die Tabellen die darin benötigt werden, selber mitbringt und nur die Datenbankverwaltung, sprich der Embedded Server im Hauptprogramm läuft. Dieser aber keine Ahnung haben soll welche Tabellen alles benötigt werden. Damit will ich erreichen nicht immer das Hauptprogramm anpassen zu müssen, wenn eine neue DLL dazu kommt.

_________________
Es gibt keine dummen Fragen, nur dumme Antworten.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: So 31.05.15 15:36 
Das Hauptprogramm stellt nur das Interface zur Verfügung um die Tabellen zu holen. Welche dann angefragt werden und wie viele usw. weiß das Hauptprogramm nicht. Und von welcher Datenquelle (Embedded Server, MS SQL Server, ...) weiß die DLL nicht.
jackle32 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 183
Erhaltene Danke: 7

Win7
Delphi XE5 Starter, RAD Studio XE7 Pro
BeitragVerfasst: Do 09.07.15 18:29 
Hallo,

nach etwas längerer Zeit bin ich dazu gekommen deinen Sourcecode umzusetzen.

Ich habe es auch soweit, dass es läuft.

Hier mal meine aktuelle Interface Unit:

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
unit MyInterfaces;

interface

uses
  System.SysUtils, Data.DB, System.Classes;

const
  IID_MyInterface: TGUID = '{9760A9AD-0600-417E-9004-A5FD92CE496B}';

type

  IMyInterface = interface
    ['{9760A9AD-0600-417E-9004-A5FD92CE496B}']

    function HoleDataset(): TDataset;
    function DatenAktivieren(): boolean;
  end;

TErstesInterface = class(TInterfacedObject, IMyInterface)

private
   FDataset: TDataset;

public
  constructor Create(const ADataset: TDataset);
  destructor Destroy; override;

  function HoleDataset(): TDataset;
  function DatenAktivieren(): boolean;
end;

implementation

{ TErstesInterface }
constructor TErstesInterface.Create(const ADataset: TDataset);
begin
  inherited Create;
  self.FDataset := ADataset;
end;

function TErstesInterface.DatenAktivieren: boolean;
var
  temp: boolean;
begin
  temp := false;

  if self.FDataset <> nil then
  begin
    self.FDataset.Open;
    temp := true;
  end;

  result := temp;
end;

destructor TErstesInterface.Destroy;
begin
  self.FDataset.Destroy;
  inherited Destroy;
end;

function TErstesInterface.HoleDataset: TDataset;
begin
  result := self.FDataset;
end;

end.


Was mir allerdings nicht wirklich klar ist, wozu ich das DatabaseGateway verwenden/gebrauchen soll?

In meiner Anwendung sieht es dann so aus, dass das Hauptprogramm ein Instanz von TErstesInterface bekommt. Dort wird im Create direkt eine Datenbanktabelle (vom Typ FDTable) als Dataset übergeben. Damit hab ich doch alles was ich brauche? Oder ist das eine unschöne Programmierung?

Gruß,
Jack

_________________
Es gibt keine dummen Fragen, nur dumme Antworten.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Do 09.07.15 20:29 
Du kannst keine Objekte zwischen DLL und Anwendung austauschen. Das habe ich doch oben schon geschrieben. Mit TDataset wirst du da nicht weit kommen.
jackle32 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 183
Erhaltene Danke: 7

Win7
Delphi XE5 Starter, RAD Studio XE7 Pro
BeitragVerfasst: Do 09.07.15 22:30 
Ich tausche ja keine Objekte aus.

Ich gebe an meine DLL nur eine Referenz auf mein Interface in der Prozedur "SetInterface". Ich dachte damit unterscheide ich mich nicht von deinem Vorschlag.
Und es funktioniert ja auch. Ist das dann Zufall und nicht zu empfehlen?

Gruß,
Jack

_________________
Es gibt keine dummen Fragen, nur dumme Antworten.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Fr 10.07.15 07:26 
Du hast darin aber eine Funktion HoleDataset, die ein TDataset zurückliefert. Solange du diese nicht in der DLL benutzt um ein TDataset aus der Hauptanwendung zu holen, funktioniert es, ja. Ansonsten gibt es Probleme, weil du eben dieses TDataset aus der Anwendung als Objekt in der DLL nutzt.

Und genau dafür ist das IDatasetGateway:
Damit du kein TDataset in die DLL bekommst, sondern ein IDatasetGateway, das die in der DLL benötigten Funktionen kapselt.
jackle32 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 183
Erhaltene Danke: 7

Win7
Delphi XE5 Starter, RAD Studio XE7 Pro
BeitragVerfasst: Fr 10.07.15 19:10 
Ich poste hier mal den Sourcecode so wie er im Moment aussieht.

Irgendwie fehlt mir mit der GatewayDatabase Klasse der letzte Schritt im zu verstehen wo der unterschied liegt bzw. wie ich das ins Programm einbinden kann.

Falls du mal lustig bist, wäre es super ein ganz kurzes Beispiel in mein Programm einzubauen.

Bis dahin werde ich mal mit der Version von mir arbeiten, da die im Moment zu laufen scheint.

Vielen Danke bis dahin aber für deine Hilfe.

Gruß,
Jack
Einloggen, um Attachments anzusehen!
_________________
Es gibt keine dummen Fragen, nur dumme Antworten.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mo 13.07.15 06:42 
Ich schaue es mir an, aber meine Frau wird heute operiert, so dass ich aktuell keine Zeit habe. In den nächsten Tagen sollte ich ein paar Minuten Zeit finden...
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Di 14.07.15 15:08 
Im Anhang ein kleines Beispiel. Für mehr habe ich leider gerade keine Zeit. Aber es zeigt denke ich wie ich es meinte. ;-)
Einloggen, um Attachments anzusehen!