Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Übergabe einer Klasse von EXE an DLL (https://www.delphipraxis.net/193865-uebergabe-einer-klasse-von-exe-dll.html)

norwegen60 18. Sep 2017 09:08

Übergabe einer Klasse von EXE an DLL
 
Hallo zusammen,

ich habe schon diverse Beiträge zu dem Thema gefunden, aber die Angaben sind widersprüchlich.

Ich habe folgende stark vereinfachte Klassendefinition
Delphi-Quellcode:
type
  TMesswert = class(TObject)
    Belastung : Real;
    Strom    : Real;
  end;

  TMesswertListe = class( TObjectList<TMesswert> )
  public
    constructor Create( OwnsObjects: Boolean = true );
  end;

  TAnalyse = class(TObject)
  private
    FNr : Integer;
    FStatus : Integer; // In Wirklichkeit Enum
    FTitel : String;
    FTestunit : String;
    FMesswerte : TMesswertList;
  public
    property FNr   : Integer read Nr write Nr ;
    property FTitel : String read Titel write Titel ;
    property FTestunit : String read Testunit write Testunit ;
    constructor Create;
    destructor Destroy; override;
  end;

  TAnalyseList = class(TObjectList<TAnalyse> )
  private
    function Compare( const L,R: TAnalyse) : Integer;
  public
    constructor Create( OwnsObjects: Boolean = true );
  end;

  TBatch = class(TObject)
  private
    FNr : Integer;
    FAnalysen : TAnalyseList;
  public
    property FNr   : Integer read Nr write Nr ;
    constructor Create;
    destructor Destroy; override;
  end;
Im angedachten Fall soll
  • eine DLL aufgerufen und dieser die komplette Instanz von TBatch übergeben werden.
  • Die EXE erzeugt für jede gewünschte Belastung einen Eintrag in TMesswertList
  • Nach der Übergabe von TBatch an die DLL soll diese anhand von Testunit die korrekte Testunit starten und dann für jede Belastung den zugehörigen Strom messen und in der Liste eintragen.
  • Sobald fertig, wird der Status von TAnalyse auf asMeasured gesetzt und EXE weiß, dass sie jetzt damit weiter machen kann.
  • Testunit ist in Wirklichkeit eine weitere Klasse (Gerät, Schnittstelle, ...)
  • Es sollte auch möglich sein, dass die EXE eine weitere Analyse an TBatch anhängt während die DLL die schon eingetragenen Analysen abarbeit. Auch diese neue Analyse wird von DLL automatisch vermessen.

Es bestehen folgende Voraussetzungen
  • Die Klasse wird in der EXE angelegt
  • Werte sollen sowohl in der EXE als auch der DLL geändert werden
  • EXE und DLL werden bei jeder Änderung in exakt derselben Umgebung kompiliert
  • Auf Mitgabe von Packages soll wenn möglich verzichtet werden
  • Entwicklungsumgebung Delphi XE

Mein Problem ist nur die Frage, wie ich die Klasse korrekt an die DLL übergeben muss damit beide Seiten darin arbeiten können.

Gibt es ein Möglichkeit, so was zu realisieren?
Mit Klassen fühle ich mich noch unsicher und mit Interfaces habe ich noch nie gearbeitet. Von daher wäre ich für eine konkrete Antwort, an liebsten mit Beispiel, sehr dankbar.

Grüße
Gerd

sakura 18. Sep 2017 09:44

AW: Übergabe einer Klasse von EXE an DLL
 
Entweder du arbeitest mit Run-Time-Packages, auf welche beide Seiten zugreifen, da Du dann auch einen gemeinsamen Memory-Manger nutzt. Dieses wäre die Voraussetzung, um Klassen über Prozessgrenzen hinweg zu nutzen.

Oder, Du stellst auf Interfaces um, diese kannst Du dann ohne größere Probleme zwischen beiden Seiten nutzen und übergeben.

...:cat:...

Ghostwalker 18. Sep 2017 10:58

AW: Übergabe einer Klasse von EXE an DLL
 
Wie Sakura schon sagte, ist ein Austausch von Objekt-Instanzen so nicht möglich. Aber, neben der Sache mit Interfaces, wäre auch ein anderer Ansatz denkbar.

Sowie ich das verstehe, ist der Zweck der DLL, die Liste der Analysen nacheinander durch zu ackern und die entsprechenden Tests auszuführen und je nach Ergebnis für jede Analyse einen entsprechenden Status zu setzen.

Wenn die DLL jetzt nicht grad noch von einer anderen Anwendung benutz werden soll, würd ich diese über die Klinge springen lassen und statt dessen die Analyse in einen eigenen Thread auslagern, sodas dieser quasi im Hintegergrund die Analyseliste immer wieder durcharbeiten kann. Wenn eine Analyse mit dem entsprechenden Status abegschlossen worden ist, kann der Thread das Hauptprogramm benachrichtigen. Genauso kann das Hauptprogramm neue
Analysen hinzufügen.

Das ganze ist jetzt mehr eine Idee/Denkanstoss, die ich hier mal so geschrieben hab. :)

Fritzew 18. Sep 2017 11:42

AW: Übergabe einer Klasse von EXE an DLL
 
Zitat:

und statt dessen die Analyse in einen eigenen Thread auslagern
Wobei das wohl der einzige Ansatz ist der funktioniert. Da in einer Dll die Routinen ja auch im Main-Thread laufen,
braucht man sowieso einen extra Thread. Dann kann man das immer noch in eine DLL packen, zuerst sollte aber das "Problem" gelöst werden.

sakura 18. Sep 2017 11:45

AW: Übergabe einer Klasse von EXE an DLL
 
Oder eventuell in eine komplett eigene Anwendung auslagern. Die Daten in einem gemeinsamen Format (JSON, XML, binär, ...) austauschen. Dann können die Prozesse komplett autonom arbeiten.

...:cat:...

norwegen60 18. Sep 2017 12:01

AW: Übergabe einer Klasse von EXE an DLL
 
Zitat:

Zitat von Ghostwalker (Beitrag 1381421)
Sowie ich das verstehe, ist der Zweck der DLL, die Liste der Analysen nacheinander durch zu ackern und die entsprechenden Tests auszuführen und je nach Ergebnis für jede Analyse einen entsprechenden Status zu setzen.

Wenn die DLL jetzt nicht grad noch von einer anderen Anwendung benutz werden soll, würd ich diese über die Klinge springen lassen und statt dessen die Analyse in einen eigenen Thread auslagern, sodas dieser quasi im Hintegergrund die Analyseliste immer wieder durcharbeiten kann. ...

Du hast es korrekt verstanden.
Über die DLL wollen wir aber den Messprozess weitgehend von der EXE kapseln. In der DLL soll er dann in einem eigenen Thread laufen.

Ein Kollege hat mal folgendes ausprobiert:

Hauptprogramm:

Delphi-Quellcode:
uses
  ...,
  clBatch;

type
  procedure ShowTestForm(aApplication: TApplication; aAnalyse: TAnalyse); stdcall; external 'DelphiDll.dll';

implementation
  ...

procedure TfoMain.btnShowDelphiDllClick(Sender: TObject);
var
  aAnalyse: TAnalyse;

begin
  aAnalyse := GetCurrentAnalyse;
  ShowTestForm(Application, aAnalyse);
end;
Und die DLL
Delphi-Quellcode:
library DelphiDll;

{ Important note about DLL memory management: ShareMem must be the
  ...}

uses
  ShareMem,
  clBatch,
  ...;

{$R *.res}

exports
  ShowTestForm;

begin
end.

unit uFormTest;
interface

uses
 ...

type
  TFormTest = class(TForm)
    EditAnalyse: TEdit;
    Label1: TLabel;
    btnAssFilled: TButton;
    btnAssInjected: TButton;
    procedure btnAssFilledClick(Sender: TObject);
    procedure btnAssInjectedClick(Sender: TObject);
  private
    { Private declarations }
    FAnalyse: TAnaylse;
    procedure SetAnalyse(aAnalyse: TAnalyse);
  public
    { Public declarations }
    constructor Create(AOwner: TComponent);
    class procedure ShowForm(aApplication: TApplication; aAnalyse: TAnalyse);
  end;


implementation

{$R *.dfm}

uses
  ...;

var
  dllApplication: TApplication=nil;
  FormTest: TFormTest=nil;

procedure TFormTest.btnAssFilledClick(Sender: TObject);
begin
  if not Assigned(FAnalyse) then exit;
  FAnalyse.Status := assFilled;
end;

procedure TFormTest.btnAssInjectedClick(Sender: TObject);
begin
  if not Assigned(FAnalyse) then exit;
  FAnalyse.Status := assInjected;
end;

constructor TFormTest.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FAnalyse:= nil;
end;

procedure TFormTest.SetSample(aAnalyse: TAnalyse);
begin
  FAnalyse:= aAnalyse;
end;

class procedure TFormTest.ShowForm(aApplication: TApplication; aAnalyse: TAnalyse);
begin
  if not Assigned(dllApplication) then
  begin
    dllApplication:= Application;
  end;
  Application := aApplication;
  if not Assigned(FormTest) then
    FormTest := TFormTest.Create(Application);
  FormTest.FAnalyse := aAnalyse;
  if not Assigned(aAnalyse) then
    FormTest.EditAnalyse.Text := '-'
  else
    ;// EditAnalyse.Text := aAnalyse.name;
  { TODO :
    Mit der auskommentierten Zeile gibt es im folgenden eine Exception "Invalid pointer operation ..."
    was irgendetwas mit strings zu tun hat. Richtig angezeigt wird der Name dennoch.
    Die Ursache ist zu ergründen.
  }
  FormTest.Show;
end;

initialization
begin
  dllApplication := nil;
end;

finalization
begin
  if Assigned(dllApplication) then Application := dllApplication;
end;


end.
und das funktioniert.

In der DLL wird ein Formular geöffnet und darin kann man den Status der Analyse ändern.

Fragt man in der EXE den Status ab, wird dort der korrekte (geänderte) Status angezeigt.

Von daher sieht es doch ganz einfach aus?

Fritzew 18. Sep 2017 12:13

AW: Übergabe einer Klasse von EXE an DLL
 
Das wird so nicht funktionieren. Kapselt als erstes Euere Analyse sauber in einen Thread.
Das dann in eine DLL zu packen ist in meinen Augen der 2. Schritt.
Delphi-Quellcode:
class procedure TFormTest.ShowForm(aApplication: TApplication; aAnalyse: TAnalyse);
Das fliegt Euch früher oder später um die Ohren ohne Packages. Da das TApplication und TAnalyse in der EXE und DLL unterschiedlich sind. (verschiedene Vmt's etc).....
Entweder Packages, Interfaces oder eine gute alte c-schnittstelle

Ghostwalker 18. Sep 2017 16:08

AW: Übergabe einer Klasse von EXE an DLL
 
Alternativen dazu wären natürlich die "Old-School" Methode mit Records, Zeigern und Zeigerketten. Die kann man ohne Probleme zwischen Exe und DLL austauschen :)


Bsp.: (He..das is nur so ungefähr) :
Delphi-Quellcode:
TYPE
   PMesswert = ^TMesswert;
   TMesswert = Record
       Belastung, Strom : Real;
       Prev,Next : PMesswert;
   end;
   PAnalyse = ^TAnalyse;
   TAnalyse = Record
     Nr,Status : Integer;
     Titel,TestUnit : String;
     Messwerte : PMesswerte; //Zeiger auf den Anfang der Zeigerkette;
   end;

norwegen60 18. Sep 2017 17:24

AW: Übergabe einer Klasse von EXE an DLL
 
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:

Zitat von Ghostwalker (Beitrag 1381489)
Alternativen dazu wären natürlich die "Old-School" Methode mit Records, Zeigern und Zeigerketten. Die kann man ohne Probleme zwischen Exe und DLL austauschen :)

Die Klassenstruktur besteht schon. Die zurück zu ändern wäre ein sehr großer Eingriff

Zitat:

Zitat von Fritzew (Beitrag 1381435)
Das wird so nicht funktionieren. Kapselt als erstes Euere Analyse sauber in einen Thread.

Der Thread ist eine Sache, die Datenübergabe eine andere. Mir geht es in dieser Anfrage nur darum "Wie kann ich innerhalb Delphi eine Klasse übergeben" bzw. was ist an unserem Ansatz falsch.

Ich habe jetzt mal das Beispiel von meinem Kollegen auf das minimiert, was man für einen ersten Test braucht. Auch wenn die Aussage im Raum steht, dass uns das um die Ohren fliegt...
Wo ist das Problem? Ein anderer Programmierer hat dazu gemein, dass eine Klasse innerhalb Delphi auch nur ein Pointer ist und wenn beide Seiten über den Aufbau genau das gleiche wissen, würden beide auch gleich mit den Daten umgehen.
Ich kann sehr gut damit leben, wenn in der DLL nur die Daten zur Verfügung stehen und eventuelle Methoden tabu sind. Man muss vielleicht noch dazu sagen, dass die EXE nur noch lesend auf die Messwert-Liste zugreift.

Ich habe mal das lauffähige Porjekt als ZIP angehängt. Ich wäre echt dankbar, wenn man einen Weg finden würde, das Ganze sicher zum Laufen zu bringen. Oder zu zeigen, wo die Fallstricke im Betrieb sind

In dem Projekt kann man
  • eine Klasse mit zufälliger Unterstruktur erzeugen
  • diese in einem TreeView darstellen
  • im TreeView eine Analyse markieren
  • Diese an die DLL übergeben
  • dort den Status ändern
  • über [Read Analysis] die Änderung des Status im TreeView darstellen
Letzteres könnte man natürlich über CallBacks noch eleganter lösen aber im Moment geht es um die Klassenübergabe

Auch wenn ihr jetzt denkt "Wenn du uns nicht glaubst, dann lass es doch bleiben" Das ist es nicht. Aber vielleicht finde ich den, der mir sagt, so und so würde es gehen :-) Es ist immer schwer von etwas abzulassen, wenn es irgendwie schon funktioniert. Gleichzeitig weiß ich aber, dass das Zufall sein kann und sich ganz schnell ändern kann.

Zacherl 18. Sep 2017 17:37

AW: Übergabe einer Klasse von EXE an DLL
 
Zitat:

Zitat von norwegen60 (Beitrag 1381512)
Auch wenn die Aussage im Raum steht, dass uns das um die Ohren fliegt...
Wo ist das Problem? Ein anderer Programmierer hat dazu gemein, dass eine Klasse innerhalb Delphi auch nur ein Pointer ist und wenn beide Seiten über den Aufbau genau das gleiche wissen, würden beide auch gleich mit den Daten umgehen.

Solange du DLL und EXE mit exakt der gleichen Delphi Version kompilierst, "sollte" es funktionieren. Ob da nicht am Ende doch bei RTTI lastigen Anwendungen irgendwas um die Ohren fliegt, kann ich allerdings nicht genau sagen.

Edit: Wobei .. wenn ich grade nochmal so drüber nachdenke, dann könnte es diverse statische Speicherbereiche bzw. Singletons geben, die dann zwischen DLL und EXE nicht geshared werden würden. Das kann auch Probleme machen.

mkinzler 18. Sep 2017 17:37

AW: Übergabe einer Klasse von EXE an DLL
 
Deshalb besser Interfaces benutzen.

norwegen60 18. Sep 2017 20:23

AW: Übergabe einer Klasse von EXE an DLL
 
Mmh, wie schon weiter oben gesagt, habe ich mich mit Interfaces noch nicht beschäftigt.
Ich habs mir heute mal angeschaut, verstehe aber nicht ganz wieso es mir im Fall Delphi zu Delphi helfen soll.

Wie würden mein Projekt sich denn Ändern, wenn es per Interfaces laufen würde?

Zacherl 18. Sep 2017 21:36

AW: Übergabe einer Klasse von EXE an DLL
 
Zitat:

Zitat von norwegen60 (Beitrag 1381526)
Wie würden mein Projekt sich denn Ändern, wenn es per Interfaces laufen würde?

Im Grunde nicht viel. Du müsstest dir für jedes Object alle Methoden, die über die Programmgrenzen hinweg aufgerufen werden in einem Interface deklarieren. Danach leitest du dein bestehendes Objekt nicht mehr von
Delphi-Quellcode:
TObject
, sondern von
Delphi-Quellcode:
TInterfacedObject
und
Delphi-Quellcode:
IMyInterface
ab.

norwegen60 18. Sep 2017 21:55

AW: Übergabe einer Klasse von EXE an DLL
 
Zitat:

Zitat von Zacherl (Beitrag 1381527)
Im Grunde nicht viel. Du müsstest dir für jedes Object alle Methoden, die über die Programmgrenzen hinweg aufgerufen werden in einem Interface deklarieren. Danach leitest du dein bestehendes Objekt nicht mehr von
Delphi-Quellcode:
TObject
, sondern von
Delphi-Quellcode:
TInterfacedObject
und
Delphi-Quellcode:
IMyInterface
ab.

Hier liegt eines meiner Verständnisprobleme. Eigentlich will ich ja gar keine Methoden über die Programmgrenzen hinweg aufrufen sondern nur Felder. Ausser wenn bei den Properties auch die Getter und Setter gemeint sind. Wäre auch ein
Delphi-Quellcode:
property Power: Realread FPowerwrite FPower;
betroffen?
Oder ein
Delphi-Quellcode:
  TMessValue = class(TObject)
    Load: Real;
    Power: Real;
  end;

Zacherl 18. Sep 2017 22:50

AW: Übergabe einer Klasse von EXE an DLL
 
Interfaces können leider ausschließlich Methoden besitzen. Zugriff auf Felder müsstest du dann alle über Getter/Setter kapseln. Ich würde allerdings mal behaupten, dass wenn du in deiner Klasse ausschließlich triviale Datentypen hast (String und Arrays sind davon ausgenommen) und die Klasse zudem keine Seiteneffekte in der VCL/RTL auslöst, solltest du relativ sicher sein. Höchstens irgendwelche RTTI Geschichten könnten dann noch Probleme bereiten.

jaenicke 19. Sep 2017 05:18

AW: Übergabe einer Klasse von EXE an DLL
 
Es gibt viele Stolpersteine, wenn man Klassen so über DLL-Grenzen hinaus benutzt. Das fängt schon bei einem simplen is bzw. as an, das nicht funktioniert. Das wird aber teilweise auch in den Quelltexten der RTL und VCL benutzt. Deshalb erlebt man da durchaus die eine oder andere Überraschung...

Die Umstellung auf Interfaces lohnt sich. Damit ist man zukunftssicher unterwegs und muss nicht mit Workarounds anfangen, wenn es Probleme gibt. Zumal die Fehlersuche im Zweifelsfall enorm Zeit kostet.

Dazu kommt, dass man die Schnittstelle zur DLL auch bei Erweiterungen nicht ändern muss, sondern auf verschiedene Versionen eines Interfaces prüfen kann, so also zwischen verschiedenen Versionen der DLL und des Hostprogramms kompatibel ist. Wenn man das denn möchte.

Vor allen braucht man sich aber nicht darum zu kümmern, dass der Speicher der übergebenen Objekte nicht zu früh oder zu spät freigegeben wird usw., da das automatisch passiert, wenn die Referenzen z.B. auf nil gesetzt werden. Gerade bei mehreren beteiligten Modulen ist das sehr hilfreich.

Für die generischen Listen usw. haben wir z.B. Container mit Interface-Anbindung erstellt.

Ghostwalker 20. Sep 2017 09:58

AW: Übergabe einer Klasse von EXE an DLL
 
Ich häng mich jetzt einfach mal an. Hat das ganze schon jemand mal mit den "Advanced Records" probiert ?

norwegen60 20. Sep 2017 11:32

AW: Übergabe einer Klasse von EXE an DLL
 
Zitat:

Zitat von jaenicke (Beitrag 1381533)
Dazu kommt, dass man die Schnittstelle zur DLL auch bei Erweiterungen nicht ändern muss, sondern auf verschiedene Versionen eines Interfaces prüfen kann, so also zwischen verschiedenen Versionen der DLL und des Hostprogramms kompatibel ist. Wenn man das denn möchte.

Für uns ist es keine Einschränkung, dass EXE und DLL immer in derselben Umgebung kompiliert werden müssen.

Zitat:

Zitat von jaenicke (Beitrag 1381533)
Vor allen braucht man sich aber nicht darum zu kümmern, dass der Speicher der übergebenen Objekte nicht zu früh oder zu spät freigegeben wird usw., da das automatisch passiert, wenn die Referenzen z.B. auf nil gesetzt werden. Gerade bei mehreren beteiligten Modulen ist das sehr hilfreich.

Auch das ist keine Einschränkung. Der Speicher, sprich erstellen und freigeben, wird immer nur in der EXE gemacht. Aktuell ist die Vorstellung, dass die EXE den gesamten Prüfablauf und die Felder für erwartete Ergebnisse generiert. Die DLL füllt dann diese Felder mit Messergebnissen. Im Beispiel die Power bei der vorgegebenen Prüflast.
Das wäre was anderes, wenn man auch die Intelligenz, den Prüfablauf zu generieren, in die DLL verlegen würde was wir aber vermieden haben um einen eindeutigen Master of the Class zu haben. Eine Einschränkung mit der wir leben können, wenn dadurch das Klassenhandling sicher ist.

jus 20. Sep 2017 18:46

AW: Übergabe einer Klasse von EXE an DLL
 
Zitat:

Zitat von jaenicke (Beitrag 1381533)
Es gibt viele Stolpersteine, wenn man Klassen so über DLL-Grenzen hinaus benutzt. Das fängt schon bei einem simplen is bzw. as an, das nicht funktioniert. Das wird aber teilweise auch in den Quelltexten der RTL und VCL benutzt. Deshalb erlebt man da durchaus die eine oder andere Überraschung...

Die Umstellung auf Interfaces lohnt sich. Damit ist man zukunftssicher unterwegs und muss nicht mit Workarounds anfangen, wenn es Probleme gibt. Zumal die Fehlersuche im Zweifelsfall enorm Zeit kostet.

Dazu kommt, dass man die Schnittstelle zur DLL auch bei Erweiterungen nicht ändern muss, sondern auf verschiedene Versionen eines Interfaces prüfen kann, so also zwischen verschiedenen Versionen der DLL und des Hostprogramms kompatibel ist. Wenn man das denn möchte.

Vor allen braucht man sich aber nicht darum zu kümmern, dass der Speicher der übergebenen Objekte nicht zu früh oder zu spät freigegeben wird usw., da das automatisch passiert, wenn die Referenzen z.B. auf nil gesetzt werden. Gerade bei mehreren beteiligten Modulen ist das sehr hilfreich.

Für die generischen Listen usw. haben wir z.B. Container mit Interface-Anbindung erstellt.

Hallo,
die Auslagerung von Funktionen mit Objekten in ein paar eigenständige DLLs hatte ich auch schon mal vor gehabt, darum würde mich interessieren wie sowas ausschauen könnte. Könnte jemand ein kurzes Beispiel posten wie sowas ausschaut?

lg,
jus

Fritzew 20. Sep 2017 21:40

AW: Übergabe einer Klasse von EXE an DLL
 
Zitat:

Auch das ist keine Einschränkung. Der Speicher, sprich erstellen und freigeben, wird immer nur in der EXE gemacht. Aktuell ist die Vorstellung, dass die EXE den gesamten Prüfablauf und die Felder für erwartete Ergebnisse generiert. Die DLL füllt dann diese Felder mit Messergebnissen.
Da sind wir aber auch schon wieder beim gleichen Problem... Irgendwie müsst ihr ja sicherstellen das die Daten "Threadsafe" sind.
Meiner Meinung nach ist Euer Ansatz falsch. Wenn eine Dll so abhängig ist von der EXE, sprich gleicher Compiler Source etc. Dann schafft ihr nur zusätzliche Probleme.
Ich würde empfehlen im ersten Schritt das Threadsafe zu machen. Erst dann anschauen was man sinnvoll und unabhängig in eine DLL verschieben kann. Ich sehe sonst keinerlei Sinn in einer DLL.
Am einfachsten wäre eigentlich das benutzen von Packages in der Konstellation. Dafür sind die nämlich gemacht.

norwegen60 20. Sep 2017 23:18

AW: Übergabe einer Klasse von EXE an DLL
 
Zitat:

Zitat von jus (Beitrag 1381741)
Hallo,
die Auslagerung von Funktionen mit Objekten in ein paar eigenständige DLLs hatte ich auch schon mal vor gehabt, darum würde mich interessieren wie sowas ausschauen könnte. Könnte jemand ein kurzes Beispiel posten wie sowas ausschaut?

lg,
jus

Auf der 1. Seite dieses Threads ist ein komplettes, lauffägies Projekt, in dem eine EXE und eine DLL enthalten sind und die funktionieren. Die große Frage ist nur, wie sicher es funktioniert.

Zitat:

Zitat von Fritzew (Beitrag 1381744)
Da sind wir aber auch schon wieder beim gleichen Problem... Irgendwie müsst ihr ja sicherstellen das die Daten "Threadsafe" sind.
Meiner Meinung nach ist Euer Ansatz falsch. Wenn eine Dll so abhängig ist von der EXE, sprich gleicher Compiler Source etc. Dann schafft ihr nur zusätzliche Probleme.
Ich würde empfehlen im ersten Schritt das Threadsafe zu machen. Erst dann anschauen was man sinnvoll und unabhängig in eine DLL verschieben kann. Ich sehe sonst keinerlei Sinn in einer DLL.
Am einfachsten wäre eigentlich das benutzen von Packages in der Konstellation. Dafür sind die nämlich gemacht.

Threadsafe ist eines der Themen. Es geht aber um die Abkopplung von Funktionalitäten, bei dem im Fehlerfall auch mal nur die DLL getauscht werden kann. Klar, dabei darf sich die Klasse nicht geändert haben. Das ist eine Einschränkung. Die Entwicklungsumgebung dagegen nicht. Das war x Jahre Delphi 7 und seit 2011 Delphi XE. Also genügend Zeit dazwischen um die gleiche Umgebung sicherzustellen. Zumal die releasten Teile immer in derselben Umgebung erstellt werden. Kann gut sein, dass wir im Zuge Klassen und DLL auf XE10.2 gehen, aber dann ist wieder ein paar Jahre Ruhe.

jus 21. Sep 2017 01:37

AW: Übergabe einer Klasse von EXE an DLL
 
Zitat:

Zitat von norwegen60 (Beitrag 1381747)
Zitat:

Zitat von jus (Beitrag 1381741)
Hallo,
die Auslagerung von Funktionen mit Objekten in ein paar eigenständige DLLs hatte ich auch schon mal vor gehabt, darum würde mich interessieren wie sowas ausschauen könnte. Könnte jemand ein kurzes Beispiel posten wie sowas ausschaut?

lg,
jus

Auf der 1. Seite dieses Threads ist ein komplettes, lauffägies Projekt, in dem eine EXE und eine DLL enthalten sind und die funktionieren. Die große Frage ist nur, wie sicher es funktioniert.

Ups sorry :oops: , habe das Wort "Interface" vergessen reinzuschreiben. Und zwar war meine Frage, wie kann man ein Objekt sauber über Interface zu einer DLL übergeben? Könnte jemand da ein Beispiel posten?

lg,
jus

Fritzew 21. Sep 2017 08:23

AW: Übergabe einer Klasse von EXE an DLL
 
Eines der Probleme die ich in der Verwendung von Klassen in Exe und Dll auch schon erlebt habe:
Da die EXE und DLL komplett unabhängig von einander compiliert werden, kann es passieren das der
Linker, (Stichwort Smart Linking) auf die Idee kommt Teile der Klasse, anders anzuordnen oder sogar wegzulassen wenn Sie nicht benutzt
oder anders benutzt werden. Ihr verlasst Euch darauf das das Ergebnis von Compile und Link auf beiden Seiten immer das selbe ist.
Kann funktionieren muss aber nicht. Das schöne daran im Debug Modus wird es zu 99.9 % immer funktionieren, im Release........ (Viel Spass beim suchen)

Also meiner Meinung nach:
  1. Packages,
  2. Interfaces
  3. oder POD (Plain old Data) über records...
Alles andere führt irgendwann zu Problemen

norwegen60 25. Nov 2017 09:06

AW: Übergabe einer Klasse von EXE an DLL
 
Zitat:

Zitat von Fritzew (Beitrag 1381749)
Alles andere führt irgendwann zu Problemen

Wir haben jetzt mal einen kompletten Prozess so umgesetzt. Im Main wird ein kompletter Prüfablauf definiert. Teile des Prüfablaufs werden von der Main gesteuert und durchgeführt. Aber ein Teil der Prüfungen wird duch eine DLL abgearbeitet, der dieser Prüfablauf als Objekt übergeben wurde.
  • Durch die Übergabe weiß die DLL welchen Treiber sie verwenden muss
  • Durch Stati an den Prüfpunkten weiß sie wann sie zu messen hat
  • Die DLL schreibt die Messwerte in diese Prüfpunkte
  • Wenn sie mit einem Prüfpunkt fertig ist setzt sie den Stati hoch
  • Die Main steuert anhand der zurückgegebenen Stati den weiteren Verlauf
  • Bisher werden in der DLL keinerlei Daten created oder gefreet. Das unterliegt ausschließlich der Main. Die DLL schreibt nur Werte in bestehende Datenlisten.
Einziges Problem, auf das wir bisher gestoßen sind, ist das IS und AS in der DLL nicht funktionieren.
Ansonsten scheint bisher alles zu klappen. Egal ob wir im Debugmode kompilieren oder in Bamboo als Release.
Eigentlich ist es eine tolle Sache und ich habe auch noch einen Programmierer getroffen, die das schon länger so praktizieren. Anscheinend problemlos.

Und doch muss ich sagen, dass mir eure Komentare und viele Funde in div. Foren Kopfzerbrechen bereiten. Ich bin nicht 100% sicher, dass es nachher im Feld unter allen Bedingungen sicher funktioniert.

Zitat:

Zitat von jus (Beitrag 1381748)
Und zwar war meine Frage, wie kann man ein Objekt sauber über Interface zu einer DLL übergeben? Könnte jemand da ein Beispiel posten

Da ich niemanden zur Verfügung habe, der sich schon mal mit Interfaces beschäftigt hat, wäre ich auch sehr an einem Beispiel interessiert.


Grüße
Gerd

DeddyH 25. Nov 2017 10:40

AW: Übergabe einer Klasse von EXE an DLL
 
OK, mal schnell heruntergeschludert (kann also noch Denkfehler enthalten, funktionierte aber bei einem schnellen Test): zunächst ein Interface mit einer Property nebst Getter und Setter und einer Methode.
Delphi-Quellcode:
unit TestIntf;

interface

uses System.Classes;

type
  ITestIntf = interface
    ['{AE7A35E3-5DB3-4DEB-A817-E452DD62301C}']
    function GetItems: TStrings; stdcall;
    procedure SetItems(const Value: TStrings); stdcall;
    procedure ShowContents; stdcall;
    property Items: TStrings read GetItems write SetItems;
  end;

implementation

end.
Dieses Interface wird sowohl in der DLL als in der Exe benutzt. Jetzt zur DLL, Klassenunit:
Delphi-Quellcode:
unit DLLClass;

interface

uses TestIntf, System.Classes;

type
  TTest = class(TInterfacedObject, ITestIntf)
  strict private
    FItems: TStrings;
  public
    function GetItems: TStrings; stdcall;
    procedure SetItems(const Value: TStrings); stdcall;
    procedure ShowContents; stdcall;
    property Items: TStrings read GetItems write SetItems;
  end;

function GetTest: ITestIntf; stdcall;

implementation

uses Vcl.Dialogs;

function GetTest: ITestIntf; stdcall;
begin
  Result := TTest.Create;
end;

{ TTest }

function TTest.GetItems: TStrings;
begin
  Result := FItems;
end;

procedure TTest.SetItems(const Value: TStrings);
begin
  FItems := Value;
end;

procedure TTest.ShowContents;
var
  s: string;
begin
  if Assigned(FItems) then
    s := FItems.Text
  else
    s := '< Keine Items zugewiesen >';
  ShowMessage(s);
end;

end.
Das ist also eine minimale Klasse, die das Interface imlementiert. Zu beachten ist auch die Funktion GetTest, diese wird in der Hauptunit der DLL exportiert.
Delphi-Quellcode:
library IntfDLL;

uses
  DLLClass in 'DLLClass.pas';

{R *.res}

exports
  GetTest;

begin
end.
In der Exe habe ich dann diese DLL einfach statisch gebunden:
Delphi-Quellcode:
unit ExeMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, TestIntf, Vcl.StdCtrls;

type
  TfrmDLLTestMain = class(TForm)
    btnCallIntf: TButton;
    procedure btnCallIntfClick(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  frmDLLTestMain: TfrmDLLTestMain;

implementation

{$R *.dfm}

function GetTest: ITestIntf; stdcall; external 'IntfDLL.dll' name 'GetTest';

procedure TfrmDLLTestMain.btnCallIntfClick(Sender: TObject);
var
  List: TStringList;
  Test: ITestIntf;
begin
  Test := GetTest;
  List := TStringList.Create;
  try
    List.Add('Das');
    List.Add('ist');
    List.Add('das');
    List.Add('Haus');
    List.Add('vom');
    List.Add('Nikolaus');
    // Einmal vor der Zuweisung
    Test.ShowContents;
    Test.Items := List;
    // Und einmal nachher
    Test.ShowContents;
  finally
    List.Free;
  end;
end;

end.
Und nur der Vollständigkeit halber noch die *.dpr, da steht auch keine Magic drin:
Delphi-Quellcode:
program IntfTest;

uses
  Vcl.Forms,
  ExeMain in 'ExeMain.pas' {frmDLLTestMain};

{$R *.res}

begin
  ReportMemoryLeaksOnShutdown := true;
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TfrmDLLTestMain, frmDLLTestMain);
  Application.Run;
end.

norwegen60 25. Nov 2017 13:21

AW: Übergabe einer Klasse von EXE an DLL
 
Zitat:

Zitat von DeddyH (Beitrag 1387147)
OK, mal schnell heruntergeschludert (kann also noch Denkfehler enthalten, funktionierte aber bei einem schnellen Test)

Danke. Damit ist das Wochenende gerettet. Jetzt habe ich endlich was zu tun :-)
Mal schauen, was das für unsere Struktur bedeutet

Fritzew 25. Nov 2017 13:32

AW: Übergabe einer Klasse von EXE an DLL
 
Zitat:

Zitat von DeddyH (Beitrag 1387147)
OK, mal schnell heruntergeschludert (kann also noch Denkfehler enthalten, funktionierte aber bei einem schnellen Test): zunächst ein Interface mit einer Property nebst Getter und Setter und einer Methode.
Delphi-Quellcode:
unit TestIntf;

interface

uses System.Classes;

type
  ITestIntf = interface
    ['{AE7A35E3-5DB3-4DEB-A817-E452DD62301C}']
    function GetItems: TStrings; stdcall;
    procedure SetItems(const Value: TStrings); stdcall;
    procedure ShowContents; stdcall;
    property Items: TStrings read GetItems write SetItems; // Meiner Meinung nach böse eine Klasse zu übergeben wenn auch abstract
  end;

implementation

end.

Einspruch!!
Das ist kein gutes Design. Du mixt hier Interfaces und Implementation

Durch
Delphi-Quellcode:
 property Items: TStrings read GetItems write SetItems;
bist Du an einen Compiler gebunden.
Ein Interface sollte nur solche benutzen.

Delphi-Quellcode:
unit myInterfaces;

interface
   uses System.Classes;

type
  // Wenn wir nur über Window sprechen ist Dies ein Ansatz:
  IStringList = interface
     ['{143A95EE-EEEA-4F7F-97CC-7986EBAC17A5}']
     function AddString(const Value: WideString): Integer; stdcall;
     function GetCount: Integer; stdcall;
     function GetItems(Index: Integer): WideString; stdcall;
     procedure SetItems(Index: Integer; const Value: WideString); stdcall;
     property Count: Integer read GetCount;
     property Items[Index: Integer]: WideString read GetItems write SetItems;
  end;

   // Für non Windows müssen wir auf PlainData ztrückgreifen
   ICharBufferInterface = interface
    ['{D6CAF8DE-7792-4162-B472-E9F0A2410201}']
    procedure SetBuffer(const Buffer: PWideChar; Len : integer); stdcall;
    procedure GetBuffer(const Buffer: PWideChar; Len : integer); stdcall;
    function BufferLen : integer;
  end;

  IBufferList = interface
     ['{6FCA6674-3BE2-4D02-A078-F3142B1A41C1}']
     function GetCount: Integer; stdcall;
     function GetItems(Index: Integer): ICharBufferInterface; stdcall;
     procedure SetItems(Index: Integer; Value: ICharBufferInterface); stdcall;
     property Count: Integer read GetCount;
     property Items[Index: Integer]: ICharBufferInterface read GetItems write SetItems;
     function AddItem: ICharBufferInterface; stdcall;
  end;

implementation

end.
Hier noch eine mögliche implementierung einer Klasse dazu:

Delphi-Quellcode:
unit myImplementation;

interface
    uses System.Classes,
         myInterfaces;


 type TMyWideStringListImpl = class(TInterfaceList, IStringList)

 private
    fList : TStringList;
    function checkIndex(index : integer) : boolean; inline;

  private   // From IStringList
    function AddString(const Value: WideString): Integer; stdcall;
    function GetCount: Integer; stdcall;
    function GetItems(Index: Integer): WideString; stdcall;
    procedure SetItems(Index: Integer; const Value: WideString); stdcall;

  public
    constructor Create;
    destructor Destroy; override;
 end;

implementation

function TMyWideStringListImpl.AddString(const Value: WideString): Integer;
begin
   result := flist.Add(Value);
end;

function TMyWideStringListImpl.GetCount: Integer;
begin
   result := fList.Count;
end;

function TMyWideStringListImpl.GetItems(Index: Integer): WideString;
begin
 if checkindex(index) then
  result := Flist[Index]
  else result := '';
end;


procedure TMyWideStringListImpl.SetItems(Index: Integer; const Value: WideString);
begin
   Flist[Index] := Value;
end;

constructor TMyWideStringListImpl.Create;
begin
  inherited;
  fList := TStringList.create;
end;

destructor TMyWideStringListImpl.Destroy;
begin
  fList.free;

  inherited;
end;

function TMyWideStringListImpl.checkIndex(index: integer): boolean;
begin
   result := (index >= 0) and (index<Flist.count);
end;

end.

Fritzew 26. Nov 2017 11:16

AW: Übergabe einer Klasse von EXE an DLL
 
Ich habe mir mal die Zeit genommen das so aufzubereiten wie ich es für richtig halte.
es geht mir darum zu zeigen wie die Interfaces meiner Meinung nach aussehen sollten.

Zur vereinfachung habe ich mal 2 Deiner Klassen als Basis genommen.
Die Klasse Tmesswert habe ich um einen Info String erweitert.


Mit diesem Ansatz sind im NormalFall so gut wie keine Änderungen in Deinen konkreten Klassen notwendig
und sind komplett Compilerunabhängig. Die Dll kann also auch mit einem anderen Compiler erstellt werden
ohne das Probleme zu erwarten sind.

Delphi-Quellcode:
unit Analyse.Defaults;

interface
  uses classes, Generics.collections;
 // Hier nur zur verdeutlichung
 type
  TMesswert = class(TObject)
    Belastung : Real;
    Strom : Real;
    Info : String;
  end;

  TMesswertListe = class( TObjectList<TMesswert> )
  end;

implementation
end.
Als nächstes die Interfaces dazu:

Delphi-Quellcode:
unit Analyse.Interfaces;
interface
 type
    IMesswert = interface
   ['{CAD5EAF6-D0DE-4C2A-A955-EEDE805B09F4}']
      function GetBelastung: Double; stdcall;
      function GetInfo: WideString; stdcall;
      function GetStrom: Double; stdcall;
      procedure SetBelastung(const Value: Double); stdcall;
      procedure SetInfo(const Value: WideString); stdcall;
      procedure SetStrom(const Value: Double); stdcall;
      property Belastung: Double read GetBelastung write SetBelastung;
      property Info: WideString read GetInfo write SetInfo;  // WideString wird verwendet weil da Windows das Speicherhandling übernimmt
      property Strom: Double read GetStrom write SetStrom;

   end;

  // Hier kommt alles rein was der Konsument können muss
   IMesswertList = interface
      ['{A8F39543-4F57-49EB-99B8-78DD4DBCA4B9}']
      // Wir wollen wissen wieviele Einträge es gibt...
      function GetCount: Integer; stdcall;
      // Nur LeseZugriff auf die Items
      function GetItem( index : Integer) : IMesswert; stdcall;
      // Abfragen eines Index
      function GetItemIndex(item : IMesswert) : integer; stdcall;
      // Neuen Eintrag anhängen
      function AddItem : IMesswert; stdcall;
   end;
implementation
end.
Hier die Implementierung

Delphi-Quellcode:
unit Analyse.Implementations;

interface
uses
   System.sysutils,
   System.classes,
   Analyse.Interfaces,
   Analyse.Defaults;

// Hier definieren wir 2 Wrapper Kalssen für die Interfaces

type
  TIMesswert = class(TInterfacedObject, IMesswert)
   private
    FMesswert : TMesswert;
    function GetBelastung: Double; stdcall;
    function GetInfo: WideString; stdcall;
    function GetStrom: Double; stdcall;
    procedure SetBelastung(const Value: Double); stdcall;
    procedure SetInfo(const Value: WideString); stdcall;
    procedure SetStrom(const Value: Double); stdcall;
   protected
     function getMesswert : TMesswert;
   public
    constructor Create(aMesswert : TMesswert);
    destructor Destroy; override;
  end;


  TIMesswertListWrapper = class(TInterfacedObject, IMesswertList)
   private
     Flist : TMesswertListe;
     function AddItem: IMesswert; stdcall;
     function GetCount: Integer; stdcall;
     function GetItem(index : Integer): IMesswert; stdcall;
     function GetItemIndex( item : IMesswert): integer; stdcall;
   public
    constructor Create(const aList : TMesswertListe );
    destructor Destroy; override;
  end;

implementation

function TIMesswert.GetBelastung: Double;
begin
   Result := FMesswert.Belastung;
end;

function TIMesswert.GetStrom: Double;
begin
   Result := FMesswert.Strom;
end;

procedure TIMesswert.SetBelastung(const Value: Double);
begin
   Fmesswert.Belastung := Value;
end;

procedure TIMesswert.SetStrom(const Value: Double);
begin
   FMesswert.Strom := Value;
end;

function TIMesswert.getMesswert: TMesswert;
begin
  result := FMesswert;
end;

constructor TIMesswert.Create(aMesswert: TMesswert);
begin
  inherited create;
  FMesswert := aMesswert;
end;

destructor TIMesswert.Destroy;
begin
  FMesswert := nil;
  inherited;
end;

function TIMesswert.GetInfo: WideString;
begin
  result := FMesswert.Info;
end;

procedure TIMesswert.SetInfo(const Value: WideString);
begin
  FMesswert.Info := Value;
end;

{ TIMesswertListWrapper }

constructor TIMesswertListWrapper.Create(const aList: TMesswertListe);
begin
  inherited Create;
  Assert(Flist = nil,'Liste muss übergeben werden');
  Flist := aList;
end;

destructor TIMesswertListWrapper.Destroy;
begin
  flist := nil;
  inherited;
end;

function TIMesswertListWrapper.AddItem: IMesswert;
var lMesswert : TMesswert;
begin
  lMesswert := TMesswert.create;
  Flist.add(lMesswert);
  result := TIMesswert.Create(lMesswert);
end;

function TIMesswertListWrapper.GetCount: Integer;
begin
  result := Flist.count;
end;

function TIMesswertListWrapper.GetItem(index : Integer): IMesswert;
begin
   // TODO -cMM: index prüfen
   result := TIMesswert.Create(flist[index]);
end;

function TIMesswertListWrapper.GetItemIndex(item : IMesswert): integer;
begin
// TODO -cMM: Gültigkeit von item prüfen
   result := Flist.IndexOf((item as TIMesswert).getmesswert);
end;

end.
Und nun zur Benutzung

Delphi-Quellcode:
unit Analyse.Worker;

interface

uses
  Analyse.Defaults;
  type
    tAnalyseWork = class
       public
         class function doAnalyse(aList : TMesswertListe) : boolean;
    end;

implementation

uses
  Analyse.Interfaces,
  Analyse.Implementations;

 // Nur als Dummy hier
  function DllFunc(List : IMesswertList) : boolean; stdcall; // external whatever
  begin
     result := false;
  end;

{ tAnalyseWork }
class function tAnalyseWork.doAnalyse(aList: TMesswertListe): boolean;
var lWorker : TIMesswertListWrapper;
begin
  lworker := TIMesswertListWrapper.Create(Alist);
  try
      // Aufruf der Dll
     result := Dllfunc(lworker);
  finally
     lworker := nil;
  end;
end;
end.

stahli 26. Nov 2017 18:33

AW: Übergabe einer Klasse von EXE an DLL
 
@norwegen60

Interfaces sind kein Hexenwerk.
Wenn Du etwas Zeit dafür hast dann schau sie Dir mal an.
Vielleicht hilft Dir das etwas:
http://www.delphipraxis.net/192364-t...nterfaces.html
http://www.delphipraxis.net/183702-i...-factorys.html

jus 29. Nov 2017 15:13

AW: Übergabe einer Klasse von EXE an DLL
 
Zitat:

Zitat von DeddyH (Beitrag 1387147)
OK, mal schnell heruntergeschludert (kann also noch Denkfehler enthalten, funktionierte aber bei einem schnellen Test): zunächst ein Interface mit einer Property nebst Getter und Setter und einer Methode.

...

Zitat:

Zitat von Fritzew (Beitrag 1387177)
Ich habe mir mal die Zeit genommen das so aufzubereiten wie ich es für richtig halte.
es geht mir darum zu zeigen wie die Interfaces meiner Meinung nach aussehen sollten.

Zur vereinfachung habe ich mal 2 Deiner Klassen als Basis genommen.
Die Klasse Tmesswert habe ich um einen Info String erweitert.


Mit diesem Ansatz sind im NormalFall so gut wie keine Änderungen in Deinen konkreten Klassen notwendig
und sind komplett Compilerunabhängig. Die Dll kann also auch mit einem anderen Compiler erstellt werden
ohne das Probleme zu erwarten sind.

...

Ahh... habe erst jetzt die Lösungen entdeckt :oops:, vielen vielen Dank an Fritznew and DeddyH für den Code!!!! :thumb:

lg,
jus


Alle Zeitangaben in WEZ +1. Es ist jetzt 00:17 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz