Title: DLL+
Question: Export an object-reference from a DLL is one approach
to get real OO-access to a DLL. The DLL must create and return
the object, so the client gets the methods without encapsulating.
Let's see how this framework, called DLL+, works:
Answer:
There is also an example uploaded with UML-Diagrams (*.tif) from ModelMaker. We always work with MM since we had to redesign a big project.
First, we have to built an abstract class in a separate unit (see below the same with a real interface). You might consider this unit like an interface:
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;
Second we built the DLL in a new unit (*.dpr) with the corresponding class, which has to implement the methods from the interface:
type
TIncomeReal = class(IIncome)
private
FRate: Real;
public
constructor Create;
function GetIncome(const aNetto: Currency): Currency; override;
procedure SetRate(const aPercent, aYear: integer); override;
...
3. And now comes the export, cause objects in DLL+ are created by calling a global Constructor function, you can see that returning objects from the function that creates them is acceptable by the client:
{-------------------------------------------------------------}
function CreateIncome: TIncomeReal; stdcall;
begin result:= TIncomeReal.Create; end;
exports CreateIncome resident;
begin {fake} end.
{-------------------------------------------------------------}
4. At last we take a look at the client. In managing objects the client consider who owns the object and is responsible for freeing it up:
Uses income1;
private
IncomeRef: IIncome; //member
...
function CreateIncome:IIncome; stdcall; external('income.dll');
...
procedure TfrmIncome.FormCreate(Sender: TObject);
begin
IncomeRef:=createIncome;
end;
5. So the access is easy, stable and improves maintanance of the DLL. It should be mandatory to implement a function called queryDLLInterface, in order to reproduce the interface and all the parameters in case of lost (see example).
procedure TfrmIncome.BitBtnOKClick(Sender: TObject);
begin
incomeRef.SetRate(strToInt(edtZins.text),
strToInt(edtJahre.text));
cIncome:= incomeRef.GetIncome(StrToFloat(edtBetrag.Text));
edtBetrag.text:= Format('%m',[cIncome]);
end;
See also: http://www.delphi3000.com/articles/article_1416.asp
or the book: "UML mit Delphi" (in german)
Calling an Interface
-------------------------------------------------------------------
1. Now the client calls an InterfaceReference:
private
incomeIntRef: IIncomeInt;
procedure TfrmIncome.BitBtnOKClick(Sender: TObject);
begin
incomeIntRef:=createIncome;
try
with incomeIntRef do begin
if QueryInterface(IIncomeInt, incomeIntRef) = S_OK then begin
SetRate(strToInt(edtZins.text),
strToInt(edtJahre.text));
cIncome:=strTofloat(edtBetrag.text);
2. The Unit Income1 is enlarged with the interface:
IIncomeInt = interface (IUnknown)
['{DBB42A04-E60F-41EC-870A-314D68B6913C}']
function GetIncome(const aNetto: Currency): Currency; stdcall;
function GetRate: Real;
function queryDLLInterface(var queryList: TStringList):
TStringList; stdcall;
procedure SetRate(const aPercent, aYear: integer); stdcall;
property Rate: Real read GetRate;
end;
3. The DLL exports now a real Interface-Pointer:
TIncomeRealIntf = class (TInterfacedObject, IIncomeInt)
private
FRate: Real;
function Power(X: Real; Y: Integer): Real;
protected
function GetRate: Real;
public
constructor Create;
destructor destroy; override;
function GetIncome(const aNetto: Currency): Currency; stdcall;
function queryDLLInterface(var queryList: TStringList):
TStringList; stdcall;
procedure SetRate(const aPercent, aYear: integer); stdcall;
property Rate: Real read GetRate;
end;
function CreateIncome: IIncomeInt; stdcall;
begin
result:= TIncomeRealIntf.Create;
end;
4. When n-classes implements one interface push a parameter to the export routine in the DLL:
function CreateIncome(intfID: byte): IIncomeInt; stdcall;
begin
case intfID of
1: result:= TIncomeRealIntf.Create;
2: result:= TIncomeRealSuper.Create;
3: result:= TIncomeRealSuper2.Create;
end;
end;
exports CreateIncome;
client-calling looks like this:
incomeIntRef:=createIncome(3);