max@kleiner.com

                                                                                                           

1       DLL+

Vom Exportieren einer Objektreferenz aus einer DLL heraus. Nun, wir erarbeiten ein Rahmenwerk, welches mit Hilfe einer Schnittstellenroutine eine solche Referenz exportiert, somit einem Client ein objektorientierter und standardisierter Zugriff erlaubt. Dieses Framework nennt sich DLL+.

1.1  Export von Klassen

Als erstes benötigen wir eine DLL als Service, die exemplarisch einen Zinseszins berechnen kann. Unser Ziel ist es, eine Referenz zu exportieren, so dass andere Anwendungen auf die gewünschten Methoden der DLL zugreifen können.

 

Eigentlich kann ObjectPascal kein Objekt aus einer DLL exportieren, ausser wir setzen virtuelle Methoden ein. Jedes Objekt führt ja einen Zeiger auf die eigene virtuelle Methodentabelle (VMT) mit, so dass sozusagen durch die Hintertüre die aufrufende Anwendung in den Besitz der Adresse der virtuellen Methoden kommt.

 

DLL+ besteht also aus zwei Units:

 

Die Library income.dpr beinhaltet als DLL die eigentlichen konkreten Methoden und die zu exportierende Referenz als Funktion.

Die Unit income1.pas, beinhaltet als eine Schnittstelle die abstrakte Klasse.

 

// 8_income_class.tif

Das Klassendiagramm der exportfähigen DLL

 

Die Unit income1.pas ist wie eine Objektschnittstelle zu betrachten, welche abstrakte Methoden definiert, die von einer anderen Klasse, nämlich der Klasse TIncomeReal, implementiert werden können. Die richtigen Schnittstellen werden auch wie abstrakte Klassen deklariert.

 

Sie lassen sich aber nicht direkt instantiieren und verfügen auch nicht über eigene Methoden. Schnittstellen haben auch keine Konstruktoren oder Destruktoren. Instanziert werden sie durch Klassen, über die sich die Methoden der Schnittstelle implementieren lassen.

 

Vom Konzept her ist das unser Fall, denn income1.pas besteht nur aus folgendem Rumpf:

 

unit income1;
interface

type
IIncome = class
public
function GetIncome(const aNetto: Currency): Currency; virtual; abstract;
procedure SetRate(const aPercent, aYear: integer);
                                   virtual; abstract;

function queryDLLInterface(var queryList: TStringList): TStringList;
                                   virtual; abstract;
end;

 

Kommen wir nun zur eigentlichen Library income.dpr, die ja von der Schnittstelle, d.h. von der Klasse IIncome, erbt. Hier lassen sich die Methoden nach Wunsch und Anforderung implementieren. Die Referenz auf die Klasse läßt sich dann mit Hilfe der globalen Funktion CreateIncome und dem zugehörigen Constructor exportieren.

 

Als Namenskonvention empfiehlt es sich ein Create vor den DLL-Namen zu setzen, also CreateIncome.

 

Type

TIncomeReal = class(IIncome)

private
 FRate: Real;

public
constructor Create;
function GetIncome(const aNetto: Currency):
  Currency; override;
procedure SetRate(const aPercent, aYear: integer);
 override;

 

Der eigentliche Export befindet sich in einer Funktion, die als Rückgabewert die Referenz über den Constructor der Klasse zurückgibt. In der Export Table der DLL, lässt sich z.B. mit der Schnellansicht unter NT die Funktion CreateIncome als einzigen Entry-Point lokalisieren.

 

Dann benötigt man nur noch das Schlüsselwort exports und fertig ist das Framework DLL+:

function CreateIncome: TIncomeReal; stdcall;
begin
  result := TIncomeReal.Create;
 end;
{ Export der Schnittstellenroutine }
{----------------------------------------------------}
exports CreateIncome resident;

 

1.2  Client holt die Referenz

Nachdem die DLL als income.dll compiliert wurde (achten Sie auf die Gross/Kleinschreibung beim Funktionsnamen), bauen wir uns den Client. Der Client benötigt vor allem die Datei income1.dcu, die somit statisch gelinkt wird.

Also doch ein Haken, da eine normale DLL ohne weitere Dateien beim Entwickler / Aufrufer zum Einsatz kommt.

Nun, dies ist der Preis des Exportes, da sowohl die DLL als auch der Client die gleiche Klassenstruktur als abstrakte Klasse definieren müssen, ähnlich einer Typelibrary unter COM oder Corba.

 

Nur so kann ein Eintrag in die virtuelle Methodentabelle erfolgen. Instanziert wird nicht IIncome, sondern die DLL mit der Nachfolgerklasse TIncomeReal:

 

Uses income1;
private
    IncomeRef: IIncome; //member

function CreateIncome: IIncome; stdcall;
                              external('income.dll');

procedure TfrmIncome.FormCreate(Sender: TObject);
begin
  IncomeRef:=createIncome;
end;

 

 

//8_income_seq.tif

Sind wir erst im Besitz der Referenz, lassen sich die Methoden direkt aufrufen.

1.3  Alles COM oder was ?

Dieses Exportieren hat klar den Vorteil, dass künftig nicht jede einzelne Methode einer DLL im Client deklariert werden muss, sondern das Arbeiten mit Referenzen ist flexibler, übersichtlicher und führt nebst modularer Architektur auch zu strukturierterem Code.

 

Eine DLL wird sozusagen als Service Provider betrachtet. Was uns im Vergleich zu COM natürlich fehlt, ist der ganze Prozess der Registrierung und schlussendlich der Aufruf über die Rechnergrenzen hinweg, d.h. der Netzfähigkeit wie bei DCOM. In den meisten Fällen genügt aber DLL+ / SO+ (in Kylix) den Ansprüchen.

 

Der Client ist nun in der Lage, das Objekt mittels des Zeigers direkt aufrufen zu können, d.h. die Methoden der DLL direkt einzusetzen. Neue Funktionen lassen sich später der DLL hinzufügen oder verbessern, der Client linkt einfach das angepasste Interface neu hinzu.

 

Eine nützliche Erweiterung in unserem Fall ist der Einbau einer Stringliste, die mit einem standardisierten Aufruf wie queryDLLInterface() jedem Aufrufer oder Client zeigt, welche Methoden und Parameter die DLL zur Verfügung stellt.

qList:= TStringList.create;
   with memo1.lines do begin
      addStrings(incomeRef.queryDLLInterface(qList));

 

DLL+ sieht vor, eine leere Stringliste zu übergeben, so dass diese Liste von der vorbereiteten DLL mit den nötigen Typisierungen der Methoden und Signaturen gefüllt wird. So läßt sich im Notfall auch mal eine nicht vorhandene Interfacedatei wieder reproduzieren.

Diese Technik ist auch mit Interfaces (Delphi/Kylix) und mit C++ möglich.

 

Max Kleiner