Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi Plugins: Datenaustausch zwischen DLL und Hauptprogramm (https://www.delphipraxis.net/142018-plugins-datenaustausch-zwischen-dll-und-hauptprogramm.html)

alleinherrscher 20. Okt 2009 23:19


Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Hi, ich versuche mich gerade in das doch ziemlich spannende Gebiet der DLLs einzuarbeiten, da ich darüber nachdenke, für mein kleines Netzwerkprogramm eine Plugin Schnittstelle zu schaffen. Allerdings bin ich bis jetzt ganz gut ohne DLL programmierung zurecht gekommen - ich bin also DLL-Anfänger (bitte Rücksicht nehmen! :-D )

Ich habe ein bisschen im Forum rumgesucht und bin auf Luckies Plugin Demo gestoßen, die ja schonmal eine sehr gute Einführung liefert.

Ich stelle mir dabei vor, dass man kleine Programme, wie z.B. ein Whiteboard (-> man zeichnet eine Grafik und diese wird dann auf allen Netzwerkrechnern angezeigt), in einer DLL verpackt und mein Netzwerktool quasi die Netzwerkkommunikation leistet.

Warum so kompliziert und die Netzwerkkommunikation nicht direkt in das kleine Programm integrieren? - Weil ich eben alles in einem Programm bündeln möchte und in dem Netzwerktool schon eine recht leistungsstarke Netzwerkkommunikation eingebaut habe.

Um größere Datenmengen zu handeln benutzt mein Netzwerkprogramm Memorystreams und da dachte ich mir, es wäre sinnvoll, auch die Kommunikation zwischen Hauptprogramm und DLL über diesen Datentyp laufen zu lassen.

Jetzt meine Frage:

1. Ist es technisch überhaupt möglich, diesen Datentyp zwischen DLL und Hauptprogramm auszutauschen, da ja ein memorystream im Endeffekt ein reservierter Speicherbereich ist, der einem bestimmten Prozess gehört? Kommt an dieser Stelle evtl. ShareMEM zum Einsatz - ich hab gelesen, das soll ziemlich langsam sein?

2. Ist es möglich (z.B. wenn man sich ein Event definiert) aus der Dll eine procedure im Hauptprogramm aufzurufen -> Irgendwie muss das Hauptprogramm ja anfangen, die erhaltenen Daten abzuschicken...

Vielen Dank dass ihr mir unwissenden Person helft,

Euer Michael

himitsu 21. Okt 2009 00:31

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Objekte (Klassen) mit DLLs zu Teilen ist so nicht möglich, da jeder seine eigene RTTI hat.

Also die Klassen/Typen sind nicht direkt Kompatibel, selbst wenn sie gleich definiert sind.

Und dann hat standardmäßig auch noch jeder seine eigene Speicherverwaltung, welche ebenfalls nicht miteinander arbeitet,

http://www.delphipraxis.net/internal...t.php?t=166651
http://www.delphipraxis.net/internal...t.php?t=162452
http://www.delphipraxis.net/internal...t.php?t=161358


Wo das alles gehn würde, das wären BPLs.


Ansonsten: statt Klassen verwendet man hier Interfaces
allerdings bleibt hier immernoch ein kleines Problem mit deinem Speicher.

Aber da könntest du dir den Stream ebenfalls als Interface erstellen,
welchem dann beim Auslesen ein Speicherblock vom jeweiligen Modul (DLL/EXE), bzw. von dessen Speicherverwaltung gegeben wird, wo er die Daten reinkopiert, welches bei vielen Streams allerdings eh oftmals schon so gemacht wird. :)


Man könnte (wenn es unbedingt nötig ist) den Stream so erstellen, daß er sich Speicher direkt von Windows (VirtualAlloc, MMF und Co.) besorgt und diesen Speicher kann man dann auch ganz leicht üerall in der ganzen Anwendung verwenden und komplett weiterreichen.



Und zu 2.
Ja, das mit den Callbackprozeduren kannst du hier auch ganz einfach lösen ... das geht genauso, wie sonst auch.
Entweder als normale Prozedur und hier dürfte sogar Methoden möglich sein, so wie du es von der VCL (z.B. Button.OnClick) kennst.
Aber in Bezug darauf, daß man hier DLLs auch mit anderen Sprachenn (nicht immer nur Delphi) erstellen kann,

wäre es praktisch, wenn du Interfaces und als Callback einfach Prozeduren via StdCall verwendest.
Oder du gibst beim Start der DLL, bzw. beim Erstellen (Createn) des PlugIns selber wiederum ein Callback-Interface an.

Dieses Callback-Interface kapselt dann einfach alle Befehle, welche das Plugin in der Hauptanwendung aufrufen kann.

alleinherrscher 21. Okt 2009 10:39

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Hey, vielen Dank!

Ich habe auch noch das hier gefunden und werde mich da erstmal durcharbeiten...

http://www.delphipraxis.net/internal...ect.php?t=4203

Viele Grüße,
Michael

sirius 21. Okt 2009 10:52

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Zu deinem TMemoryStream gibt es ja auch IStream. Ist quasi schon alles vorbereitet, wie bei Biolek ;)

alleinherrscher 21. Okt 2009 11:07

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Zitat:

Zitat von sirius
Zu deinem TMemoryStream gibt es ja auch IStream. Ist quasi schon alles vorbereitet, wie bei Biolek ;)

Hmmmm, das ist ja Klasse! *Wasser im Mund zusammenläuft* :bouncing4: Jetzt noch einen guten Wein dazu....hmmmmm!

Das werd ich direkt mal probieren! Melde mich, sobald die erste Probleme auftauchen!! Vielen Dank, ich glaube du hast mir viel arbeit erspart... (jetzt muss ich nur noch komplett dahinter steigen ;-)

himitsu 21. Okt 2009 12:24

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Delphi-Quellcode:
IHost = interface;
alle Funktionen/Ereignisse, welcher ein Plugin im Hauptprogramm aufrufen muß

Delphi-Quellcode:
THost = class(TObject, IHost);
das interne Objekt der Anwendung

Delphi-Quellcode:
IPlugin = interface;
die Schnittstelle zum Plugin, welche jedes Plugin implementiert
hier sind die Funktionen drinnen, welche die Anwendung im Plugin aufrufen muß/kann

Delphi-Quellcode:
TPlugin = class(TObject, IPlugin);
das interne Objekt des Plugins

Delphi-Quellcode:
function GetPlugin(Host: IHost): IPlugin; StdCall;
begin
  Result := TPlugin.Create(Host);
end;
das wäre dann z.B. 'ne Funktion, welche die Plugin-DLL exportiert, womit die Anwendung ihr Callback-Interface mitteilt und als Ergebnis das Interface zum Plugin bekommt.

Die Objekte bleiben in ihrem jeweiligem Bereich und der Andere (EXE/DLL) bekommt immer nur das Interface


für den Stream
entweder TStreamAdapter, welcher irgendeinen Nachfahren von TStream kapseln kann (also auch TMemoryStream)

oder du leitest die TMemoryStream ab und implementierst selber das Interface

Delphi-Quellcode:
type
  IDelphiStream = interface['{65805750-623E-4719-AD79-A30FF6FCA3CA}']
    procedure SetSize(NewSize: Longint);
    function Write(const Buffer; Count: Longint): Longint;
    function Read(var Buffer; Count: Longint): Longint;
    function Seek(Offset: Longint; Origin: Word): Longint;
    procedure Clear;
    //procedure LoadFromStream(Stream: IDelphiStream);
    //procedure SaveToStream(Stream: IDelphiStream);
    //procedure LoadFromFile(const FileName: WideString);
    //procedure SaveToFile(const FileName: WideString);
    property Position: Int64 read GetPosition write SetPosition;
    property Size: Int64 read GetSize write SetSize64;
  end;
  TInterfacedMemoryStream = class(TMemoryStream, IDelphiStream);
nur die auskommentierten Methoden müßte man notfalls in TInterfacedMemoryStream noch implementieren, da sie vom "Original" abweichen.
> IDelphiStream ist klar, da man hier ja nicht kein TStream verwenden kann
> warum nicht IStream ... dessen definition weicht sehr stark von TStream ab (IStream = Windows und TStream = Borland/Delphi)
> WideString ist der einzige StringTyp, welchen man ohne Einschränkung über Modulgrenzen (EXE-DLL) hinweg nutzen kann (abgesehn von PChar, aber da muß man auch aufpassen und es geht nicht alles)

alleinherrscher 21. Okt 2009 13:58

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Okay, danke! Ich denke, ich hab es soweit verstanden. Was schonmal läuft: Ich kann Plugins laden und gegenseitig Procedures aufrufen. Bin jetzt gerade dabei, den IDelphiStream zu implementieren. Müssen dabei nicht auch die Funktionen "GetPosition", "SetPosition", "GetSize" und "SetSize64" im Interface angegeben werden?

[edit] Ansonsten kennt er die in der Interface-Unit nicht: "[Fehler] InterfaceDefinition.pas(21): E2168 Feld- oder Methodenbezeichner erwartet" [//edit]

himitsu 21. Okt 2009 14:28

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
die dort schon eingetragenen Funktionen sind schon im MemoryStream vorhanden,
aber ich glaub du hast Recht, da diese Getter/Setter ja als Private nicht zur Verfügung stehn :(

aber du brauchst ja auch nur das implementieren, welches man am Ende auch benutzt
(hab hier einfach mal "alle" nötigen Standardfunktionen kopiert)


nja, schön wäre es, wenn man von 2 Objekten erben könne
TMemoryStream und TInterfacedObject, dann brächte man nur noch diese Drei implementieren
Delphi-Quellcode:
function GetPosition: Int64;
procedure SetPosition(const Pos: Int64);
procedure SetSize64(const NewSize: Int64);

jetzt könnte man also von TInterfacedObject erben und hätte die Interfaceverwaltung, muß dann aber den "echten" TMemoryStream mit einbauen und an diesen alles weiterleiten, also alle Stream-Funktionen neu implementieren/umleiten

oder eben so (vom Stream erben und "nur" noch das Basis-Interface reinbauen)
> die Funktionen von IInterface muß jedes Interface bereitstellen, da sie zur Verwaltung gehören

Delphi-Quellcode:
type
  IDelphiStream = interface
    ['{65805750-623E-4719-AD79-A30FF6FCA3CA}']
    {private}
    function GetPosition: Int64;
    procedure SetPosition(const Pos: Int64);
    procedure SetSize64(const NewSize: Int64);
    function GetSize: Int64;
    {public}
    procedure SetSize(NewSize: Longint);
    function Write(const Buffer; Count: Longint): Longint;
    function Read(var Buffer; Count: Longint): Longint;
    function Seek(Offset: Longint; Origin: Word): Longint;
    procedure Clear;
    //procedure LoadFromStream(Stream: IStream);
    //procedure SaveToStream(Stream: IStream);
    //procedure LoadFromFile(const FileName: WideString);
    //procedure SaveToFile(const FileName: WideString);
    property Position: Int64 read GetPosition write SetPosition;
    property Size: Int64 read GetSize write SetSize64;
  end;

  TInterfacedMemoryStream = class(TMemoryStream, IDelphiStream, IInterface)
  private
    function GetPosition: Int64;
    procedure SetPosition(const Pos: Int64);
    procedure SetSize64(const NewSize: Int64);
  protected
    FRefCount: Integer;
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
    class function NewInstance: TObject; override;
    property RefCount: Integer read FRefCount;
  end;

function TInterfacedMemoryStream.GetPosition: Int64;
begin
  Result := inherited Position;
end;

procedure TInterfacedMemoryStream.SetPosition(const Pos: Int64);
begin
  inherited Position := Pos;
end;

procedure TInterfacedMemoryStream.SetSize64(const NewSize: Int64);
begin
  inherited Size := NewSize;
end;

function TInterfacedMemoryStream.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

function TInterfacedMemoryStream._AddRef: Integer;
begin
  Result := InterlockedIncrement(FRefCount);
end;

function TInterfacedMemoryStream._Release: Integer;
begin
  Result := InterlockedDecrement(FRefCount);
  if Result = 0 then
    Destroy;
end;

procedure TInterfacedMemoryStream.AfterConstruction;
begin
  InterlockedDecrement(FRefCount);
end;

procedure TInterfacedMemoryStream.BeforeDestruction;
begin
  if RefCount <> 0 then
    System.Error(reInvalidPtr);
end;

class function TInterfacedMemoryStream.NewInstance: TObject;
begin
  Result := inherited NewInstance;
  TInterfacedMemoryStream(Result).FRefCount := 1;
end;
Delphi-Quellcode:
procedure TForm1.FormCreate(Sender: TObject);
var
  Stream: IDelphiStream;
begin
  Stream := TInterfacedMemoryStream.Create;
end;
wegen dem AfterConstruction und BeforeDestruction nicht wundern, das ist nur dafür da, damit das Objekt/Interface nicht mitten im Create wieder freigegeben wird, da durt durch ein paar "Problemchen" der Referenzzähler kurz auf 0 runterkommen kann (z.B. wenn man das erstellte Objekt/Interface an eine Objektvariable übergibt gibt und nicht SOFORT an eine Interfacevariable)


da es leider kein Private bei Interfaces gibt:
der Trick ist mir mal eingefallen ... so sind über IDelphiStreamIntern die "privaten"/internen Definitionen nicht sichtbar ... tauchen also auch nicht in der Autovervolständigung auf :angel2:
Delphi-Quellcode:
type
  IDelphiStreamIntern = interface
    {private}
    function GetPosition: Int64;
    procedure SetPosition(const Pos: Int64);
    procedure SetSize64(const NewSize: Int64);
    function GetSize: Int64;
  end;
  IDelphiStream = interface(IDelphiStreamIntern)
    ['{65805750-623E-4719-AD79-A30FF6FCA3CA}']
    procedure SetSize(NewSize: Longint);
    function Write(const Buffer; Count: Longint): Longint;
    function Read(var Buffer; Count: Longint): Longint;
    function Seek(Offset: Longint; Origin: Word): Longint;
    procedure Clear;
    //procedure LoadFromStream(Stream: IStream);
    //procedure SaveToStream(Stream: IStream);
    //procedure LoadFromFile(const FileName: WideString);
    //procedure SaveToFile(const FileName: WideString);
    property Position: Int64 read GetPosition write SetPosition;
    property Size: Int64 read GetSize write SetSize64;
  end;
(das ist soein Trick, welchen ich im Zusammenhang mit meinen XML-Klassen mal gelernt hatte)

alleinherrscher 21. Okt 2009 15:12

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Herzlichen Dank - komplett Verstanden aber da wäre ich nie selber drauf gekommen. Interfaces sind nützlicher als gedacht! Ich habe zum test mal folgendes programmiert:


Delphi-Quellcode:
//Hauptanwendung schickt ein Bild in einem Memorystream an DLL:
procedure TForm1.Button1Click(Sender: TObject);
var app:tapp;
    astream:TInterfacedMemoryStream;
begin

  app:=tapp.create;
  StartPlugin('P_DLL.dll',app); //Sucht Procedur "LoadPlugin" in der DLL und übergibt den Zeiger auf APP an das Plugin.
                                //Der Zeiger auf TPlugin wird in PluginList gespeichert.

  showmessage('GetName: '+PluginList[0].Getname); //Funktioniert einwandfrei

  astream:=TInterfacedMemoryStream.Create;
  astream.LoadFromFile('C:\Autumn Leaves.jpg');

  PluginList[0].ReceiveStream(astream);

end;
Delphi-Quellcode:
//DLL erhält Stream korrekt und schickt ihn direkt mal wieder zurück an Hauptanwendung (einfach nur als Test der "Durchgängigkeit"):
procedure TPlugin.ReceiveStream(aStream: IDelphiStream); stdcall;
  begin
    App.AddStream(astream);

  end;
Delphi-Quellcode:
//Hauptanwendung erhält Stream wieder zurück:
procedure TApp.AddStream(aStream: IDelphiStream); stdcall;
begin
 TInterfacedMemoryStream(astream).position:=0;
 TInterfacedMemoryStream(astream).SaveToFile('C:\test2.jpg');
end;
Allerdings wird bei "TInterfacedMemoryStream(astream).position:=0; " eine Access Violation hervorgerufen, die ich noch nicht verstehe.
Hast du da evtl noch eine Idee?

:coder2:

himitsu 21. Okt 2009 15:20

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
wie gesagt, es ist ein Interface und das ist was "ganz" anderes, wie ein Objekt.

man kommt auch nicht so leicht auf das Objekt zurück (eigentlich garnicht), da nach außen egal ist, was hinter dem Interface steckt ... es ist halt nur eine Schnittstelle zu irgendwas anderem.

Delphi-Quellcode:
procedure TApp.AddStream(aStream: IDelphiStream); stdcall;
begin
  astream.position:=0;
  astream.SaveToFile('C:\test2.jpg');
end;
hier also die Funktionen vom Interface nutzen ;)

alleinherrscher 21. Okt 2009 15:22

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Zitat:

Zitat von himitsu
Delphi-Quellcode:
procedure TApp.AddStream(aStream: IDelphiStream); stdcall;
begin
  astream.position:=0;
  astream.SaveToFile('C:\test2.jpg');
end;

Das war auch mein erster Versuch. Aber da astream ja vom Typ IDelphiStream ist, besitzt er die Methode SavetoFile gar nicht... :gruebel:

//edit: ich mein...das is nicht sooo schlimm...Hauptsache der Stream kommt an...ich kann ihn ja per Tmemorystream.copyfrom in einen memorystream kopieren, denke ich mal...
//edit2: Naja, auch nicht wirklich, da sogesehen ja IDelphiStream keine Ableitung von TStream ist.
//edit3: Ich bin doof...sorry, dafür hattest du ja extra die auskommentierten Methoden eingefügt im Interface Idelphistream...D.h. da müsste ich dann noch sowas wie Copyfrom mit einbauen...

himitsu 21. Okt 2009 15:33

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
drum sagte ich ja, daß man diese eventuell noch implementieren muß
(hatte es vorhin nur nicht gemacht, da ich alles Vorhandene erstmal nur zusammenkopiert hatte und nichts direkt "neu" schrieb)

Delphi-Quellcode:
type
  IDelphiStreamIntern = interface
    {private}
    function GetPosition: Int64;
    procedure SetPosition(const Pos: Int64);
    procedure SetSize64(const NewSize: Int64);
    function GetSize: Int64;
  end;
  IDelphiStream = interface(IDelphiStreamIntern)
    ['{65805750-623E-4719-AD79-A30FF6FCA3CA}']
    procedure SetSize(NewSize: Longint);
    function Write(const Buffer; Count: Longint): Longint;
    function Read(var Buffer; Count: Longint): Longint;
    function Seek(Offset: Longint; Origin: Word): Longint;
    procedure Clear;
    procedure LoadFromStream(Stream: IDelphiStream);
    procedure SaveToStream(Stream: IDelphiStream);
    procedure LoadFromFile(const FileName: WideString);
    procedure SaveToFile(const FileName: WideString);
    property Position: Int64 read GetPosition write SetPosition;
    property Size: Int64 read GetSize write SetSize64;
  end;

  TInterfacedMemoryStream = class(TMemoryStream, IDelphiStream, IInterface)
  private
    function GetPosition: Int64;
    procedure SetPosition(const Pos: Int64);
    procedure SetSize64(const NewSize: Int64);
  protected
    FRefCount: Integer;
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
    class function NewInstance: TObject; override;
    property RefCount: Integer read FRefCount;

    procedure LoadFromStream(Stream: IDelphiStream); overload;
    procedure SaveToStream(Stream: IDelphiStream); overload;
    procedure LoadFromFile(const FileName: WideString); overload;
    procedure SaveToFile(const FileName: WideString); overload;
  end;

function TInterfacedMemoryStream.GetPosition: Int64;
begin
  Result := inherited Position;
end;

procedure TInterfacedMemoryStream.SetPosition(const Pos: Int64);
begin
  inherited Position := Pos;
end;

procedure TInterfacedMemoryStream.SetSize64(const NewSize: Int64);
begin
  inherited Size := NewSize;
end;

function TInterfacedMemoryStream.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

function TInterfacedMemoryStream._AddRef: Integer;
begin
  Result := InterlockedIncrement(FRefCount);
end;

function TInterfacedMemoryStream._Release: Integer;
begin
  Result := InterlockedDecrement(FRefCount);
  if Result = 0 then
    Destroy;
end;

procedure TInterfacedMemoryStream.AfterConstruction;
begin
  InterlockedDecrement(FRefCount);
end;

procedure TInterfacedMemoryStream.BeforeDestruction;
begin
  if RefCount <> 0 then
    System.Error(reInvalidPtr);
end;

class function TInterfacedMemoryStream.NewInstance: TObject;
begin
  Result := inherited NewInstance;
  TInterfacedMemoryStream(Result).FRefCount := 1;
end;

procedure TInterfacedMemoryStream.LoadFromStream(Stream: IDelphiStream);
var
  buf: array[0..65535] of Byte;
  i: Integer;
begin
  Clear;
  while true do
  begin
    i := Stream.Read(buf, Length(buf));
    if i = 0 then break;
    if Write(buf, i) <> i then System.Error(reOutOfMemory);
  end;
end;

procedure TInterfacedMemoryStream.SaveToStream(Stream: IDelphiStream);
var
  buf: array[0..65535] of Byte;
  i: Integer;
begin
  Stream.Clear;
  while true do
  begin
    i := Read(buf, Length(buf));
    if i = 0 then break;
    if Stream.Write(buf, i) <> i then System.Error(reOutOfMemory);
  end;
end;

procedure TInterfacedMemoryStream.LoadFromFile(const FileName: WideString);
begin
  inherited LoadFromFile(String(FileName));
end;

procedure TInterfacedMemoryStream.SaveToFile(const FileName: WideString);
begin
  inherited SaveToFile(String(FileName));
end;
LoadFromStream und SaveToStream arbeiten hier intern aber nur mit String, also bis Delphi 2007 mit AnsiString.
Wenn man auch da wirklich den WideString unterstüzen will, dann muß man sich einen Unicode-fähigen FileStream besorgen (also Unicode bei den Dateinamen).

Und direkt AnsiString/UnicodeString geht halt wegen der getrennten Speicherverwaltung nicht so einfach ... stickwort Hier im Forum suchenSharedMemoryManager

alleinherrscher 21. Okt 2009 15:43

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Klappt perfekt! Entschuldigung, dass ich im letzten Post nicht richtig aufgepasst habe, sonst hätte sich die Frage von selbst geklärt! Großartige Leistung! Ich bedanke mich recht herzlich! :cheers:

himitsu 21. Okt 2009 15:52

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
bitte bitte

und praktisch genauso, wie es jetzt mit den Stream ging,
macht man es jetzt mit den anderen Interfaces für Plugin und App
(nur daß man hier leicht direkt von TInterfacedObject erben kann und dann die Basis-Interface-Funktionen schon fertig hat)

also der Zugriff dann von außen immer nur über das jeweilige Interface

MyRealName 26. Nov 2009 15:31

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Sorry, wenn ich mich kurz einmische : Man kann sehr gut mit Klassen in DLLs arbeiten ohne die Probleme der verschiedenen RTTI etc zu haben, man muss nur wissen, wie :) Das Geheimnis sind Laufzeit-Pakete.

Ich habe eine grosse Demo mit Forms / Frames / Objects etc auf meiner Homepage. Die Demo umfasst 9 Plugins als DLL und einer Loader.exe

Einfach mal hier klicken, da gibt es das ganze als rein ausführbaren Code (Delphi2007, wers nicht hat, muss die FULL-version laden, ansonsten die Light) und als nur-source.

Um mit dem Source zu arbeiten, muss man die MAF Components runterladen und installieren, die verwalten unter anderem das ganze plugins-zeugs.

himitsu 26. Nov 2009 16:09

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Bei dieser Variante hat man dann natürlich nur eine RTTI, bezüglich dieser Komponenten ... also die in dem externen Package.

Das ist wie mit dem Speichermanager ... wenn man einen (externen) gemeinsamen Manager nutzt, dann klappt das auch, aber standardmäßig erstmal nicht.

MyRealName 26. Nov 2009 20:48

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Auch nur, weil die BPL variante uns von Anfang an zwingt, mit Laufzeit packages zu arbeiten. DLLs tun das nicht

Elvis 26. Nov 2009 22:35

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Zitat:

Zitat von MyRealName
Sorry, wenn ich mich kurz einmische : Man kann sehr gut mit Klassen in DLLs arbeiten ohne die Probleme der verschiedenen RTTI etc zu haben, man muss nur wissen, wie :) Das Geheimnis sind Laufzeit-Pakete.

Nein.
BPLs taugen eigentlich nur etwas für die IDE, da sie ja schon fest mit einer Version von Compiler, RTL und VCL verheiratet ist.
Für so ziemlich alles andere, bei dem BPLs sinnvoll wären, könnte man auch eine große Single-Exe nehmen ohne irgendwelche Flexibilität zu verlieren.
Warum? Weil BPLs so unflexibel sind, wie es entartete DLLs nur werden können ohne schon Teil der Exe zu sein.

BPLs vorauszusetzen schränkt die Schar von Autoren auf den winzigen Kreis ein, der mit exakt deiner Compiler/VCL/RTL-Version arbeitet und auch ja keine Units/Registrierte Komponentenamen mitbringt, die mit anderen Units/Komponentennamen in dem eng verwobenen Haufen BPLs kollidieren.
Kurz: Es ist fast ausgeschlossen, dass ein anderer aus dir selbst solch ein Plugin schreiben kann ohne zu riskieren, dass es irgendwann explodiert.
Zum Beispiel, weil jmd eine TntControls-Unit in einem seiner Packages hatte, anstatt EXAKT das gleiche Package zu nutzen wie alle anderen.
Aber halt: Niemand schrieb vor welche Version von Komponente XYZ genommen werden muss, weil sich die PlugIns wohl kaum alle gegenseitig die 3rd-Party-Komponenten absprechen können.

Long story short: BPLs sind die grauenvollste, instabilste und zeitverschwenderischte Art sich Flexibilität vorzutäuschen, die ich kenne.
Plugin-Systeme, die auf BPLs aufbauen sind wie Rauchen: Fange nicht damit an, wenn doch höre so schnell wie möglich auf.
Woher ich das weiß? Sagen wir's mal so: "Got the scars to prove it... :?
Siehe auch hier: http://stackoverflow.com/questions/1...705051#1705051

MyRealName 26. Nov 2009 23:12

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Ich weiss nicht, warum Du mir widersprichst, sagst doch nur etwas gegen BPLs und ich rede davon, wie man DLLs nutzen kann mit Klassen drin.
Ich bin kein Fan von BPLs und nutze sie nur für meine Komponenten selbst.
Will man allerdings wirklich modular mit Delphi programmieren, muss man mit Laufzeit-Packages arbeiten (also zumindest die Komponenten-BPLs dynamisch linken).


Das Problem mit den TNTControls kenn ich, hatte ich bei den MAF-Komponenten auch und leider hatte es mir keiner gesagt, so dass der ganze Download für ca. 4-5 Tage fürs Ar--- war, da die version nicht zu nutzen ging.
Ich hatte in meine Komponenten das package PNGComponents eingebunden, da es

1.) kostenlos von der Embarcadero Seite zu beziehen ist
und
2.) man heutzutage nicht mehr auf PNG (wegen Transparenz) in einer App verzichetn sollte/kann

hab dann leidlich durch einen Kunden rausfinden müssen, dass es wirklich nicht geht, dass man meine Komponenten mit einem selbst-erzeugtem PNGComponents package nutzen kann.
Lösung 1 wäre einfach die dcus und BPLs mitzugeben, aber wenn ich mich recht entsinne, ist das Embarcadero nicht so glücklich drüber. Also nahm ich lösung 2 und hab alles schön in Conditional Defines gekapselt, so dass man sie als source-Besizter selbst wieder reinnehmen kann und den nativen support nutzen (TPNGImageList Verwaltung, statt nur TImageList etc).

Allerdings sah ich auch durch mein Test-Programm, das sich neue Versionen von meinen Komponenten bauen konnte und meist auch ohne die Anwendung oder die DLLs zu kompilieren dann auch weiter nutzen. Man muss halt nur aufpassen, dass man keine published properties löscht ;) Oder Methoden umbenennt.

Allerdings ist zu empfehlen, vorhandene Projekte neu zu übersetzen, wenn man ein neues Package baut

Elvis 27. Nov 2009 05:52

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Zitat:

Zitat von MyRealName
Ich weiss nicht, warum Du mir widersprichst, sagst doch nur etwas gegen BPLs und ich rede davon, wie man DLLs nutzen kann mit Klassen drin.

Weil ein unschuldiger Mitleser sonst den falschen Eindruck bekäme, dass Pseudo-Modularisierug mit Packages wirkliche Flexibilität bringen würde und einem nicht 70% der Haarpracht kostet.
Zitat:

Will man allerdings wirklich modular mit Delphi programmieren, muss man mit Laufzeit-Packages arbeiten (also zumindest die Komponenten-BPLs dynamisch linken).
Eben nicht. Wirklich modular ist man, wenn die Module nicht so grauenvoll miteinander verzahnt sind sind. Und das heißt, man ist "wirklich modular" wenn man DLLs benutzt und sich in den exportierten Funktionn auf interoperable Typen wie WideString, Integer, double, Interfaces, OleVariant, etc. beschränkt.
Denn dann explodieren Module nicht stets und ständig, weil irgendein Plugin eine neue Unit nutzt, oder wenn man eine neue Delphiversion nutzen will. Ganz zu schweigen davon, dass man dann Plugins mit anderen Sprachen schreiben kann z.B.: FPC, C++, C#,...
Zitat:

Zitat von MyRealName
Allerdings sah ich auch durch mein Test-Programm, das sich neue Versionen von meinen Komponenten bauen konnte und meist auch ohne die Anwendung oder die DLLs zu kompilieren dann auch weiter nutzen. Man muss halt nur aufpassen, dass man keine published properties löscht ;) Oder Methoden umbenennt.

Allerdings ist zu empfehlen, vorhandene Projekte neu zu übersetzen, wenn man ein neues Package baut

Dann könnte man gleich eine Single-Exe nehmen ohne all die Zeit mit Package-Issues zu verschwenden.

hanspeter 27. Nov 2009 07:27

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Zitat:

Zitat von Elvis
Weil ein unschuldiger Mitleser sonst den falschen Eindruck bekäme, dass Pseudo-Modularisierug mit Packages wirkliche Flexibilität bringen würde und einem nicht 70% der Haarpracht kostet.

Da muss ich Elvis aus leidvoller Erfahrung voll zustimmen. Ich halte die BPL für den größten Design Fehler in Delphi.
Arbeite ich nur mit Dll und verwende keine Laufzeitbibliotheken, dann lauert noch eine andere Falle.
Viele Componenten registrieren ihren Namen mit Registerclass bei Windows. Ist so eine Klasse einmal registriert, kann sie in keiner weiteren dll verwendet werden.
Bestes Beispiel dll A verwendet Fastreport. Alles geht.
Jetzt wird dll B geladen, die auch den Fastreport verwendet und es knallt. (Class bereits registriert.)
Einziger Ausweg sind hier runtime - dll. Die Nachteile wurden schon geschildert.
run-time bpl hat noch einen anderen Haken. Der Linker von >= D2007 scheint nicht mehr allzu smart zu sein.
Beim Programmstart verlangt ein Programm erst mal alle bpl, die bei der Compilierung nur in der Nähe des Programms waren.
Mit einem Programm mußte ich fast 90 bpl dazu kopieren.
Bestes Beispiel. Ich arbeite mit IBDAC, IBDAC ist TDataSource kompatibel. Über diese Hintertür erwartet das Programm, dass die BDE-Laufzeitbibliothek vorhanden ist.

Wenn man sauber modularisieren will, gibt es nach meiner Meinung in Delphi nur zwei Wege. Das ist einmal ein Comserver. Einmal registriert und er tut was er soll. (Einziger _Nachteil die Registrierung.)
Eine weitere Möglichkeit ist die Modularisierung auf Exe-Basis. Eine Exe wird mit Kommandozeilen - Optionen aufgerufen.
In der Kommandozeile übergebe ich das Handle des rufenden Programms. Damit ist eine einfache IPC möglich.
Mit D2010 probiere ich gerade eine IPC auf Basis von SOAP.
Es gibt von Remobjects das Framework Hydra. Bei der Modularisierung auf Delphi-Basis hat es ebenfalls alle bereits geschilderten Nachteile.
(Benötigt Laufzeit - dll)
Was es aber zufriedenstellend löst, ist die Einbindung von Net-Assembly in Delphi.
Ich übergebe beim Aufruf den Window-Parent und kann ein Fenster nahtlos in der Delphi-Applikation einfügen.
Diesen Weg erprobe ich gerade mit WPF, da ich im Moment für eine Firma ein Konzept zur schrittweisen Ablösung von Delphi erarbeite.
Hinterrgrund der Ablösung ist übrigens weniger Delphi selbst, sondern die Tatsache das es zunehmend Schwierigkeiten in 64 bit Umgebungen gibt und
Multiplattformfähigkeiten erwartet werden.
Die Möglichkeit von Net, die Pflege der Laufzeitbibliotheken an MS zu delegieren und diese (zumindest ab XP SP2) vorraussetzen zu können ist schon verlockend.

Elvis 27. Nov 2009 09:24

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Zitat:

Zitat von hanspeter
Zitat:

Zitat von Elvis
Weil ein unschuldiger Mitleser sonst den falschen Eindruck bekäme, dass Pseudo-Modularisierug mit Packages wirkliche Flexibilität bringen würde und einem nicht 70% der Haarpracht kostet.

Da muss ich Elvis aus leidvoller Erfahrung voll zustimmen. Ich halte die BPL für den größten Design Fehler in Delphi.

Sie machen Sinn für die IDE, sie machen keinen Sinn außerhalb der IDE.

Zitat:

Arbeite ich nur mit Dll und verwende keine Laufzeitbibliotheken, dann lauert noch eine andere Falle.
Viele Componenten registrieren ihren Namen mit Registerclass bei Windows.
Falsch, RegisterClass registriert esin der RTL. Und die kann man für jede DLL & Exe getrennt haben. Ohne Laufzeitpackages sind sie sogar immer getrennt.
Zitat:

Ist so eine Klasse einmal registriert, kann sie in keiner weiteren dll verwendet werden.
Bestes Beispiel dll A verwendet Fastreport. Alles geht.
Jetzt wird dll B geladen, die auch den Fastreport verwendet und es knallt. (Class bereits registriert.)
Wie gesagt: ohne Laufzeit-Packages hast du das Problem nicht. Aber du musst dann halt mit Interfaces arbeiten, nicht mit Delphi-Klassen.
Wenn du also ein Control auf dein Form aus einer DLL laden willst, musst du per SetParent dann das Control in den jeweiligen Container packen. Aber das kann man einmalig hübsch OO verpacken, so dass die DLL nur eine Interface-reference liefern muss, die das Control verpackt und die man dann einem anderen Control per SetParent as Child hinzufügt.
Für non-visuelle macht es mehr Sinn von vorn herein auch innerhalb der Exe auf einer Interface-basierte API aufzubauen.

Zum Beispiel sowas (aus den Fingern gesaugt)
Delphi-Quellcode:
var
  menu : IMenuProvider;
  menuItem : IMenuItem;
begin
  menu := Services.GetService(IMenuProvider) as IMenuProvider;
  menuItem := menu['Edit'].AddItem('Copy');
  menuItem.Caption := '&Copy';
  menuItem.Click := @CopyClickHandler;
end;
Auf die Art hat man genau die Art Dog-Fooding, die nötig ist damit Plugins genügend Möglichkeiten haben um wirklich sinnvoll zu sein. Denn wenn die API, die auch die Plugins nutzen, nicht mächtig genug ist, merkst du das schon während du deine HauptApp schreibst.

MyRealName 27. Nov 2009 12:37

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Zitat:

Zitat von Elvis
Zitat:

Zitat von MyRealName
Ich weiss nicht, warum Du mir widersprichst, sagst doch nur etwas gegen BPLs und ich rede davon, wie man DLLs nutzen kann mit Klassen drin.

Weil ein unschuldiger Mitleser sonst den falschen Eindruck bekäme, dass Pseudo-Modularisierug mit Packages wirkliche Flexibilität bringen würde und einem nicht 70% der Haarpracht kostet.

Ich glaube, da muss man auch mal klar unterscheiden, wer denn Modularität in seinen Programmen benutzt oder benutzen will. Da sehe ich zum einen Gemeinschaftsprojekte im Internet, wo es wirklich schwer ist, alle auf dem gleichem Stand zu haben (zum Bsp. mit Delphi Version, Benutzten Komponenten etc). Und zum anderen Firmen, die ein oder mehrere Programme haben und sich Module bauen, um Funktionalität auszutauchen. Bei letzterem ist es weniger ein Problem mit den BPLs, da Firmen meist eh Delphi Lizenzen für alle Programmierer kaufen und alle auch mit Komponenten auf dem gleichem Stand sind. Ich selbst habe Jahre in einem grossen Projekt mit mehr als hundert Modulen gearbeitet, 3 Abteilungen waren daran beschäftigt, die ihre Sachen zum Modul-Pool hinzufügten und die andere Abteilungen nutzten. Das ganze natürlich mit Laufzeitpackages. Ich glaub, wir brauchten nur 4 Delphi bpls.

Zitat:

Zitat von Elvis
Zitat:

Will man allerdings wirklich modular mit Delphi programmieren, muss man mit Laufzeit-Packages arbeiten (also zumindest die Komponenten-BPLs dynamisch linken).
Eben nicht. Wirklich modular ist man, wenn die Module nicht so grauenvoll miteinander verzahnt sind sind. Und das heißt, man ist "wirklich modular" wenn man DLLs benutzt und sich in den exportierten Funktionn auf interoperable Typen wie WideString, Integer, double, Interfaces, OleVariant, etc. beschränkt.
Denn dann explodieren Module nicht stets und ständig, weil irgendein Plugin eine neue Unit nutzt, oder wenn man eine neue Delphiversion nutzen will. Ganz zu schweigen davon, dass man dann Plugins mit anderen Sprachen schreiben kann z.B.: FPC, C++, C#,...

Das was Du willst, sind kleine Funktionsserver in DLLs, wo man mal dies oder jenes berechnet.
Ich sage ganz ehrlich, dass mit Kompatiblität zu anderen Compilern oder Programmiersprachen weniger interessiert. Das, mwas ich mit Delphi nicht machen kann, hat in Modulen eh nichts zu suchen. Und eine clever gebaute Bibliothek vrhindert auch grosse Probleme mit Laufzeitpackages.
So kann man zum Bsp. sein Internetkram (z.b. durch Indy Komponenten) in nur einem Module halten, braucht kein Laufzeitpackage dafür dann und sagt dem Module, was man braucht. Praktizier ich mit Erfolg in meiner Test-Anwendung.

Zitat:

Zitat von Elvis
Zitat:

Zitat von MyRealName
Allerdings sah ich auch durch mein Test-Programm, das sich neue Versionen von meinen Komponenten bauen konnte und meist auch ohne die Anwendung oder die DLLs zu kompilieren dann auch weiter nutzen. Man muss halt nur aufpassen, dass man keine published properties löscht ;) Oder Methoden umbenennt.

Allerdings ist zu empfehlen, vorhandene Projekte neu zu übersetzen, wenn man ein neues Package baut

Dann könnte man gleich eine Single-Exe nehmen ohne all die Zeit mit Package-Issues zu verschwenden.

Nein, weil man ja die Packages nicht alle 2 Tage neu übersetzt. Es gibt ja schliesslich einen Stand, da sind die eigenen Komponenten fertig, ab da übersetzt man nicht mehr. Davor ist man insgesamt noch im Entwicklungs-Stadium und da macht es nichts aus, ob man neu übersetzt.

MyRealName 27. Nov 2009 12:58

Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
 
Zitat:

Zitat von Elvis
Für non-visuelle macht es mehr Sinn von vorn herein auch innerhalb der Exe auf einer Interface-basierte API aufzubauen.

Zum Beispiel sowas (aus den Fingern gesaugt)
Delphi-Quellcode:
var
  menu : IMenuProvider;
  menuItem : IMenuItem;
begin
  menu := Services.GetService(IMenuProvider) as IMenuProvider;
  menuItem := menu['Edit'].AddItem('Copy');
  menuItem.Caption := '&Copy';
  menuItem.Click := @CopyClickHandler;
end;
Auf die Art hat man genau die Art Dog-Fooding, die nötig ist damit Plugins genügend Möglichkeiten haben um wirklich sinnvoll zu sein. Denn wenn die API, die auch die Plugins nutzen, nicht mächtig genug ist, merkst du das schon während du deine HauptApp schreibst.

Nicht alle mögen diese Art der Programmierung, wie sie schon in der Delphi-IDE genutzt wird. Und wenn man bei einem Rein-Delphi-Programm bleiben will, dann braucht man das auch nicht.
Als ich die MAF Components gebaut habe, wollte ich diese Art der Programmierung extra vermeiden und den Leuten die Möglichkeit geben, ihren gewohnten Delphi-Stil beizubehalten. Bei mir können sich Plugins in Funktionen "reinhängen", neue Funktionen zur Verfügung stellen oder auch vorhandene "umleiten", weil die alten buggy sind oder weil man etwas für einen Kunden ändern musste, der das Standart-Verhalten nicht mochte.

Und anstelle von der obigen Version, wo jedes Plugin sich am Menü selbst anmeldet, gehe ich im Normalfall die anderen Weg herum, wo es nur eine zentrale Stelle gibt, wo Menü-Punkte angemeldet werden, die Plugins liefern sozusagen nur die Daten, mit denen sie angemeldet werden wollen. Das macht es einfacher, etwas zu ändern, da man eben nur eine Stelle hat, wo etwas passiert. So kann man zum Bsp. vom Delphi-Standart TMainMenu auf ein Menu von 3rd-party Komponenten umstellen, weil es vielelicht besser aussieht oder die Funktionen hat, die man braucht für sein Programm.


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