![]() |
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 |
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, ![]() ![]() ![]() 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. |
Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
Hey, vielen Dank!
Ich habe auch noch das hier gefunden und werde mich da erstmal durcharbeiten... ![]() Viele Grüße, Michael |
Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
Zu deinem TMemoryStream gibt es ja auch IStream. Ist quasi schon alles vorbereitet, wie bei Biolek ;)
|
Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
Zitat:
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 ;-) |
Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
Delphi-Quellcode:
alle Funktionen/Ereignisse, welcher ein Plugin im Hauptprogramm aufrufen muß
IHost = interface;
Delphi-Quellcode:
das interne Objekt der Anwendung
THost = class(TObject, IHost);
Delphi-Quellcode:
die Schnittstelle zum Plugin, welche jedes Plugin implementiert
IPlugin = interface;
hier sind die Funktionen drinnen, welche die Anwendung im Plugin aufrufen muß/kann
Delphi-Quellcode:
das interne Objekt des Plugins
TPlugin = class(TObject, IPlugin);
Delphi-Quellcode:
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.
function GetPlugin(Host: IHost): IPlugin; StdCall;
begin Result := TPlugin.Create(Host); end; 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:
nur die auskommentierten Methoden müßte man notfalls in TInterfacedMemoryStream noch implementieren, da sie vom "Original" abweichen.
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); > 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) |
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] |
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:
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)
procedure TForm1.FormCreate(Sender: TObject);
var Stream: IDelphiStream; begin Stream := TInterfacedMemoryStream.Create; end; 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:
(das ist soein Trick, welchen ich im Zusammenhang mit meinen XML-Klassen mal gelernt hatte)
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; |
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:
Allerdings wird bei "TInterfacedMemoryStream(astream).position:=0; " eine Access Violation hervorgerufen, die ich noch nicht verstehe.
//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; Hast du da evtl noch eine Idee? :coder2: |
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:
hier also die Funktionen vom Interface nutzen ;)
procedure TApp.AddStream(aStream: IDelphiStream); stdcall;
begin astream.position:=0; astream.SaveToFile('C:\test2.jpg'); end; |
Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
Zitat:
//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... |
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:
LoadFromStream und SaveToStream arbeiten hier intern aber nur mit String, also bis Delphi 2007 mit AnsiString.
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; 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 ![]() |
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:
|
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 |
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 ![]() Um mit dem Source zu arbeiten, muss man die MAF Components runterladen und installieren, die verwalten unter anderem das ganze plugins-zeugs. |
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. |
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
|
Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
Zitat:
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: ![]() |
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 |
Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
Zitat:
Zitat:
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:
|
Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
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. 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. |
Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
Zitat:
Zitat:
Zitat:
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:
Auf die Art hat man genau die Art
var
menu : IMenuProvider; menuItem : IMenuItem; begin menu := Services.GetService(IMenuProvider) as IMenuProvider; menuItem := menu['Edit'].AddItem('Copy'); menuItem.Caption := '&Copy'; menuItem.Click := @CopyClickHandler; end; ![]() |
Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
Zitat:
Zitat:
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:
|
Re: Plugins: Datenaustausch zwischen DLL und Hauptprogramm
Zitat:
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 05:41 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-2025 by Thomas Breitkreuz