Builder TFrame in Delphi-Applikation benutzen
-
Hallo,
ich habe eine mit Builder in C++ erstelle DLL, die unter anderem einen Pointer auf ein TFrame-Objekt bereitstellt, welches in der DLL erstellt wird (dient als GUI für die von der DLL bereitgestellte Funktionalität).
Mit Builder entwickelte Programme laden die DLL und können so auf extrem simple Weise die Funktionalität nutzen, indem sie einfach den Frame irgendwo einfügen.
Dieser Frame soll nun nach Möglichkeit auch in einer alten Delphi-Anwendung benutzt werden. Funktioniert nach ersten Erkenntnissen nicht. Ist auch nicht weiter verwunderlich, denn die VCL Versionen unterscheiden sich ja schon (ich hatte dennoch die Hoffnung, da ja auch die VCL im Builder keine C++-Implementation ist).
Ist dies vielleicht doch irgendwie möglich? Wenn nein, wie würde man sowas am besten lösen? Ausser das Frame anzeigen, braucht die Delphi-Anwendung nichts weiteres mit der DLL zu tun.
z.B. kann ich ohne weiteres ein TForm in der DLL anzeigen und erzeugen, auch mit einer Delphi-Anwendung als "Host". Nur resultiert dies natürlich in einem extra Fenster. Das ist leider nicht akzeptabel, da sich die GUI der DLL in die Anwendung integrieren soll (bzw es soll zumidnest so aussehen).
Im Prinzip würde es also sogar ausreichen, wenn die Delphi-Anwendung irgendwie Zeichenfläche bereitstellen würde, auf der die DLL dann selber den Frame zeichnen könnte.
-
Morle schrieb:
Ist dies vielleicht doch irgendwie möglich?
Ja, wenn:
- du mit Laufzeit-Packages und dynamischer RTL linkst,
- du den C++-Code in ein separates Package verpackst, das du mit LoadPackage() lädst,
- dein Package eine Funktion wie diese exportiert:
extern "C" __declspec (dllexport) TCustomFrameClass __stdcall getMyCppFrameClass (void) { return __classid (TMyCppFrame); }
- du die Funktion in Delphi folgendermaßen lädst:
uses SysUtils, Forms, Windows; var CppModule: HMODULE = 0; type TFrameClassFunc = function: TCustomFrameClass; function GetMyCppFrameClass: TCustomFrameClass; var FrameClassFunc: TFrameClassFunc; begin if CppModule <> 0 then CppModule := LoadPackage ('myCppModuleName.bpl'); // NOT LoadLibrary()! FrameClassFunc := TFrameClassFunc (GetProcAddress (CppModule, 'getCppFrameClass')); if not Assigned (FrameClassFunc) then raise Exception.Create ('Cannot find export ''getCppFrameClass()'' in myCppModuleName.bpl'); Result := FrameClassFunc (); end;
- du auf diesem Wege den Frame zur Laufzeit erstellst:
MyFrameComponent := GetMyCppFrameClass.Create (OwnerForm); MyFrameComponent.Parent := TheParentControl;
-
Hallo und vielen Dank für deinen Beitrag!
Allerdings funktioniert das so noch nicht. GetProcAdress scheint die Funktion nicht zu finden (ich habe den Tippfehler in deinem Quelltext beim Funktionsnamen korrigiert).
Ich sehe aber, dass die Funktion in der BPL als Export vorhanden ist (wenn ich sie mir mit Filealyzer ansehe.
-
Es könnte sein, daß die Funktion vorne noch einen Unterstrich hat: '_getMyCppFrameClass' (so kenne ich das jedenfalls bei 'extern "C"').
-
Th69 schrieb:
Es könnte sein, daß die Funktion vorne noch einen Unterstrich hat: '_getMyCppFrameClass' (so kenne ich das jedenfalls bei 'extern "C"').
Das ist nicht der Fall, wenn man __stdcall verwendet.
Das habe ich übrigens in meinem Delphi-Unit nicht berücksichtigt. Es muß natürlich so heißen:
type TFrameClassFunc = function: TCustomFrameClass; stdcall;
Morle schrieb:
Allerdings funktioniert das so noch nicht. GetProcAdress scheint die Funktion nicht zu finden (ich habe den Tippfehler in deinem Quelltext beim Funktionsnamen korrigiert).
Oh, tut mir leid, das "My" habe ich glatt vergessen. Danke für den Hinweis.
LoadPackage() wirft, wenn es fehlschlägt, üblicherweise eine Exception, daher solltest du eigentlich eine gültige Modulreferenz zurückbekommen. Aber du kannst ja mal einen Breakpoint setzen und überprüfen, ob CppModule vielleicht doch 0 ist.
-
Hallo,
ich will mich mal wieder dazu melden, da ich erst jetzt wieder dazu gekommen bin mich weiter damit zu beschäftigen.
Leider bekomme ich das so nicht zum laufen. Ich glaube es liegt an den unterschiedlichen Speicherlayouts der VCL Klassen
Ein Builder TFrame erbt ja von einer ganz anderen Klasse, nämlich der VCL Klasse des Builders. Ein TFrame aus Delphi natürlich von seinem Delphi-VCL-Pendant.
Ich kann z.B. der Parent-Eigenschaft des Frames aus der DLL in Delphi ohne Probleme ein Parent zuweisen, welches in Delphi erzeugt wurde. Nur hat das dan keinen Effekt (es passiert nichts, der Frame aus der DLL wird nicht angezeigt).
Wenn ich mir im Delphi-Debugger die Eigenschaften meines TFrames aus der DLL anschaue, sehe ich schon, das dort im Prinzip alles falsch ist, was falsch sein kann.Beispielsweise ist die "Color" Eigenschaft mit einem Wert belegt, der keinen Sinn macht. Lasse ich das Programm im Builder laufen und betrachte die Eigenschaften innerhalb der DLL noch bevor sie an Delphi übergeben werden, dann sind alle Eigenschaften richtig belegt.
-
Morle schrieb:
Ein Builder TFrame erbt ja von einer ganz anderen Klasse, nämlich der VCL Klasse des Builders. Ein TFrame aus Delphi natürlich von seinem Delphi-VCL-Pendant.
Nein; beide Klassen sind in Delphi implementiert. Wenn du mit Laufzeit-Packages linkst, sind sie identisch.
Selbstverständlich setzt das alles voraus, daß du dieselbe Delphi- und C++Builder-Version verwendest. Also etwa C++Builder 6 mit Delphi 6, C++Builder 2006 mit Delphi 2006 etc. Das sollte sich aber von selbst verstehen.
-
Die Versionen unterscheiden sich ja. Gerade das ist ja das Problem. Das hatte ich ja im Einstiegsbeitrag schon geschrieben. Da ich ja selbst schon vermutet habe, das das aufgrund der verschiedenen VCL Versionen nicht geht, hatte ich ja die Idee mit der Zeichenfläche.
Ich hatte dich wegen deiner Antwort auf den ersten Beitrag so verstanden, dass es mit Hilfe deines Prozederes trotzdem möglich sei.
-
Morle schrieb:
Die Versionen unterscheiden sich ja. Gerade das ist ja das Problem. Das hatte ich ja im Einstiegsbeitrag schon geschrieben.
Das muß mir entgangen sein. Tut mir leid.
Ein Abgleich der Versionen wäre in dieser Hinsicht bei weitem die einfachste und am leichtesten zu wartende Möglichkeit. Ist das eine Option?
Falls nicht, oder nicht kurzfristig, so gibt es natürlich dennoch Möglichkeiten für eine derartige Kooperation. Ein Ansatz wäre, in C++Builder eine Typbibliothek zu erstellen und dort eine Schnittstelle zu entwerfen, über die deine Delphi-Anwendung mit den C++Builder-Frames interagieren kann. Wichtig ist, daß über diese Schnittstelle nur COM-kompatible Typen ausgetauscht werden dürfen. Insbesondere darfst du keine RTL- und VCL-Klassen herumreichen. Einem Frame sollte sich über die TWinControl.ParentWindow-Eigenschaft ein anwendungsfremdes Fenster als Parent übergeben lassen; das Handle kannst du beispielsweise über die COM-Schnittstelle austauschen.
Du könntest auch das ganze Frame zu einer Active Form machen und über ActiveX exportieren. Allerdings würde ich eine abstraktere Schnittstelle nach obigem Muster bevorzugen.
Zu beachten ist bei derlei Unternehmungen, daß nur eine Laufzeitbibliothek dynamisch gelinkt werden darf. Also entweder die Delphi- oder die C++Builder-Anwendung mit Laufzeit-Packages und dynamischer RTL linken - nicht aber beide.
-
Hallo,
ich kann Dir nicht ganz folgen. Wieso brauche ich denn so eine komplizierte COM Schnittstelle? Mir würde es doch reichen, das Handle zu übertragen. Mehr als angezeigt werden soll das Frame ja nicht. Dann müsste ich in der DLL einfach
extern "C" __declspec(dllexport) void SetParent(HWND hWndAusDelphi) { meinFrame->ParentWindow = hWndAusDelphi; }
schreiben können um das Frame sichtbar zu machen.
In Delphi würde ich die Funktion aufrufen mit:
SetParent(myForm.Handle);
Funktioniert übrigens nicht. - Beim Zuweisen der ParentWindow-Eigenschaft gibt es eine Exception "EInvalidOperation: Element meinFrame hat kein übergeordnetes Fenster".
Das Handle, dass in der DLL ankommt ist aber gültig. Ich kann z.B. innerhalb der DLL über eine Windows-API funktion mit Hilfe des Handles das Fenster von "myForm" minimieren.BTW: Vielen Dank, dass Du soviel Geduld hast.
-
Morle schrieb:
Wieso brauche ich denn so eine komplizierte COM Schnittstelle? Mir würde es doch reichen, das Handle zu übertragen.
Ich weiß zwar nicht, was es nützen soll, ein Frame einzubinden, ohne mit ihm Daten auszutauschen - aber wenn das tatsächlich alles ist, dann reicht natürlich ein einfacher DLL-Export.
Morle schrieb:
Funktioniert übrigens nicht. - Beim Zuweisen der ParentWindow-Eigenschaft gibt es eine Exception "EInvalidOperation: Element meinFrame hat kein übergeordnetes Fenster".
Da muß ich mich selbst ans Experimentieren machen.
[...]
Offenbar kann ein Frame nicht direkt ein anderes Fenster zum Parent haben, sondern benötigt ein VCL-Fenster.
So gehts:
extern "C" { __declspec(dllexport) void* __stdcall CreateFrame (void) { std::auto_ptr<TFrame1> frame (new TFrame1 (0)); TPanel* panel = new TPanel (frame.get ()); panel->Ctl3D = true; frame->Parent = panel; return frame.release (); } __declspec(dllexport) void __stdcall DestroyFrame (void* frameObj) { TFrame1* frame = static_cast<TFrame1*> (frameObj); TWinControl* panel = frame->Parent; frame->Parent = 0; delete panel; delete frame; } __declspec(dllexport) void __stdcall SetFrameParentWindow (void* frameObj, HWND parentWnd) { TFrame1* frame = static_cast<TFrame1*> (frameObj); TWinControl* panel = frame->Parent; TRect wndRect; Win32Check (GetWindowRect (parentWnd, &wndRect)); panel->SetBounds (panel->Left, panel->Top, wndRect.Width (), wndRect.Height ()); static_cast<TFrame1*> (frameObj)->Parent->ParentWindow = parentWnd; } } // extern "C"
function CreateFrame: Pointer; stdcall; external 'EmbedFrameDll.dll'; procedure DestroyFrame (frameObj: Pointer); stdcall; external 'EmbedFrameDll.dll'; procedure SetFrameParentWindow (frameObj: Pointer; parentWnd: HWND); stdcall; external 'EmbedFrameDll.dll'; procedure TForm2.Button1Click(Sender: TObject); begin if Assigned (FEmbeddedFrame) then Exit; FEmbeddedFrame := CreateFrame; SetFrameParentWindow (FEmbeddedFrame, Panel1.Handle); end; procedure TForm2.Button2Click(Sender: TObject); begin if not Assigned (FEmbeddedFrame) then Exit; DestroyFrame (FEmbeddedFrame); FEmbeddedFrame := nil; end;
-
Klasse! Das funktioniert. Vielen vielen Dank! Auf die Idee mit dem Panel wäre ich nicht gekommen.
audacia schrieb:
Ich weiß zwar nicht, was es nützen soll, ein Frame einzubinden, ohne mit ihm Daten auszutauschen - aber wenn das tatsächlich alles ist, dann reicht natürlich ein einfacher DLL-Export.
Der Frame wird im wesentlichen nur zum Anzeigen gebraucht. Die DLL erhält das, was sie anzeigen soll, nicht von der Anwendung direkt sondern aus Dateien.