ViaThinkSoft CodeLib
This article is in:
CodeLib → Programming aids → Delphi
Für Anwendungen, bei denen Module benötigt werden, bietet sich eine DLL an. Allerdings ist die Kommunikation zwischen Host-Anwendung und DLL meist schwierig und die Objektorientierten Techniken sowie die Typsicherheit gehen aufgrund der atomaren Datentypen verloren. In dieser Vorlage zeige ich, wie man eine in der DLL erzeugte Klasse in der Hostanwendung nutzen kann.
Zuerst einmal benötigen wir ein Interface, das sowohl von der DLL als auch von der Hostanwendung genutzt wird.
ModuleInterface.pas
Nun erstellen wir eine Beispiel-Implementierung A. Später wird jede Implementierung ein Modul unserer Hostanwendung sein.
ModuleImplementationA.pas
Diese Implementierung wird nun von unserer ModuleA.DLL verwendet. Alle unsere Modul-DLLs werden eine Instanz der implementierenden Klasse erzeugen und den Zeiger darauf zurückgeben.
ModuleA.pas
Unser erstes Modul ModuleA.DLL ist nun fertig! Wir widmen uns jetzt der Hostanwendung.
Um den Gebrauch der Modul-DLL einfacher zu gestalten, habe ich folgende Unit entwickelt, die das Verwalten, also das dynamische Einbinden sowie das Freigeben aller geladenen DLLs erleichtert und den Code in der Hostanwendung auf das wesentliche Beschränkt.
ModuleManager.pas
Nun können wir unsere Hostanwendung designen. Wir nehmen hierfür ein leeres VCL Projekt, erzeugen einen Button und fügen im OnClick-Ereignis unseren Testcode ein:
Wie jetzt ein einzelnes Modul oder Plugin gestaltet wird, ist jedem selbst überlassen. Es können auch eigene VCL-Forms in die DLL eingebunden werden (demnächst mehr darüber). Ich denke, es bietet sich an, dass Module in DLL-Form in ein dafür vorgesehenes Verzeichnis kopiert werden und dort bei jedem Programmstart gesucht und automatisch eingebunden werden. Ebenso denke ich, dass es sinnvoll ist, wenn jede DLL nur 1 Klasse anbietet. Sollte ein Modul aus mehreren Teilmodulen (wie z.B. Downloadmanager, Uploadmanager, Datenbankmanager, etc.) bestehen, können diese einzelnen Klasseninstanzen als Felder in der Modul-Basis-Klasse integriert werden.
Viel Spaß!
Für Borland C++ Builder siehe http://edn.embarcadero.com/article/20165
Zuerst einmal benötigen wir ein Interface, das sowohl von der DLL als auch von der Hostanwendung genutzt wird.
ModuleInterface.pas
unit ModuleInterface;
interface
type
IMyModule = interface(IInterface)
procedure SetName(Name: string);
procedure SayHello;
end;
implementation
end.
Nun erstellen wir eine Beispiel-Implementierung A. Später wird jede Implementierung ein Modul unserer Hostanwendung sein.
ModuleImplementationA.pas
unit ModuleImplementationA;
interface
uses
ModuleInterface;
type
TModuleA = class(TInterfacedObject, IMyModule)
private
FName: string;
public
procedure SetName(Name: string);
procedure SayHello;
end;
implementation
uses
Dialogs;
{ TModuleA }
procedure TModuleA.SayHello;
begin
ShowMessageFmt('TModuleA: Hello, my name is %s!', [FName]);
end;
procedure TModuleA.SetName(Name: string);
begin
FName := Name;
end;
end.
Diese Implementierung wird nun von unserer ModuleA.DLL verwendet. Alle unsere Modul-DLLs werden eine Instanz der implementierenden Klasse erzeugen und den Zeiger darauf zurückgeben.
ModuleA.pas
library ModuleA;
uses
ModuleInterface in 'ModuleInterface.pas',
ModuleImplementationA in 'ModuleImplementationA.pas';
{$R *.res}
function CreateInstance(): IMyModule;
begin
result := TModuleA.Create();
end;
exports
CreateInstance;
end.
Unser erstes Modul ModuleA.DLL ist nun fertig! Wir widmen uns jetzt der Hostanwendung.
Um den Gebrauch der Modul-DLL einfacher zu gestalten, habe ich folgende Unit entwickelt, die das Verwalten, also das dynamische Einbinden sowie das Freigeben aller geladenen DLLs erleichtert und den Code in der Hostanwendung auf das wesentliche Beschränkt.
ModuleManager.pas
unit ModuleManager;
interface
uses
SysUtils, Windows, Contnrs, ModuleInterface;
type
TModule = class(TObject)
public
Handle: THandle;
constructor Create();
destructor Destroy(); override;
end;
TModuleManager = class(TObject)
private
LoadedModules: TObjectList; // contains TModule
public
function LoadModule(AFilename: string): IMyModule;
constructor Create();
destructor Destroy(); override;
end;
implementation
type
TDLLFunction = function(): IMyModule;
EDLLException = class(Exception);
{ TModuleManager }
constructor TModuleManager.Create;
begin
inherited;
LoadedModules := TObjectList.Create;
LoadedModules.OwnsObjects := true;
end;
destructor TModuleManager.Destroy;
begin
LoadedModules.Destroy;
inherited;
end;
function TModuleManager.LoadModule(AFilename: string): IMyModule;
var
theFunction: TDLLFunction;
buf: array [0..144] of Char;
Module: TModule;
const
functionname = 'CreateInstance';
resourcestring
LNG_LINK_ERROR = 'Unable to link to function %s in DLL %s!';
LNG_DLL_LOAD_ERROR = 'Unable to load DLL %s!' {+ #13#10 + 'Reason: %s.'};
begin
Module := TModule.Create;
Module.Handle := LoadLibrary(StrPCopy(buf, AFilename));
LoadedModules.Add(Module);
if Module.Handle <> 0 then
begin
@theFunction := GetProcAddress(Module.Handle, StrPCopy(buf, functionname));
if @theFunction <> nil then
begin
Result := theFunction()
end
else
begin
raise EDLLException.CreateFmt(LNG_LINK_ERROR, [functionname, AFilename]);
end;
end
else
begin
raise EDLLException.CreateFmt(LNG_DLL_LOAD_ERROR,
[AFilename{, DLLErrors[FHDLL]}]);
end;
end;
{ TModule }
constructor TModule.Create;
begin
inherited;
end;
destructor TModule.Destroy;
begin
if Handle <> 0 then
begin
FreeLibrary(Handle);
end;
inherited;
end;
end.
Nun können wir unsere Hostanwendung designen. Wir nehmen hierfür ein leeres VCL Projekt, erzeugen einen Button und fügen im OnClick-Ereignis unseren Testcode ein:
uses
ModuleManager, ModuleInterface;
procedure TMainForm.Button1Click(Sender: TObject);
var
ModuleManager: TModuleManager;
Module: IMyModule;
begin
ModuleManager := TModuleManager.Create;
try
Module := ModuleManager.LoadModule('ModuleA.dll');
try
Module.SetName('Johnny');
Module.SayHello;
finally
// Wichtig! Der Referenzzähler wird verringert.
// Das Objekt wird jetzt und nicht am Funktionsende
// (bei dem die DLL entladen ist) freigegeben!
Module := nil;
end;
finally
ModuleManager.Free;
end;
end;
Wie jetzt ein einzelnes Modul oder Plugin gestaltet wird, ist jedem selbst überlassen. Es können auch eigene VCL-Forms in die DLL eingebunden werden (demnächst mehr darüber). Ich denke, es bietet sich an, dass Module in DLL-Form in ein dafür vorgesehenes Verzeichnis kopiert werden und dort bei jedem Programmstart gesucht und automatisch eingebunden werden. Ebenso denke ich, dass es sinnvoll ist, wenn jede DLL nur 1 Klasse anbietet. Sollte ein Modul aus mehreren Teilmodulen (wie z.B. Downloadmanager, Uploadmanager, Datenbankmanager, etc.) bestehen, können diese einzelnen Klasseninstanzen als Felder in der Modul-Basis-Klasse integriert werden.
Viel Spaß!
Für Borland C++ Builder siehe http://edn.embarcadero.com/article/20165
Daniel Marschall
ViaThinkSoft Co-Founder
ViaThinkSoft Co-Founder