PDA

View Full Version : Exporting classes from DLLs



Snape
08-11-2002, 01:56 PM
I am looking for a way to export classes from a Delphi-dll.

The only thing I found was in the content of "The Delphi Magazine" from July 1995.
My question: Does anybody have this issue and can send me a pdf-copy (or anything similiar) or do you know the solution for my problem?

thanx,
Christoph

Zanthos
08-11-2002, 04:07 PM
Hope this helps.. :)

First we need an abstract class which your application uses to 'know' how to use the class in the DLL..


type
TBaseTest = class
public
function GetValue : Integer; virtual; abstract;
end;


This is pretty useless as a class, returning the number 1234 through a DLL has no use whatsoever, but its the principle :)

You only have to declare the public section in our abstract class, and in it all procs/funcs must be virtual and abstract. Public variables are declared in the normal way, private variables don't have to be declared.

Next, in the DLL, we create the implementation of this class, eg:


type
TBaseTestImp = class(TBaseTest)
private
DummyValue : integer;
public
function GetValue: integer; override;
end;


and the GetValue function...


function TBaseTestImp.GetValue : integer;
begin
Result := 1234;
end;


Now, create another unit of the DLL and create this function..


function CreateNewObject : TBaseTest;
begin
CreateNewObject:= TBaseTestImp.Create;
end;


This creates a new instance and returns its reference. This function must be added to the exports list in the DLL.

Compile the DLL, and now on to the EXE :)


procedure TForm1.FormCreate(Sender : TObject);
var
a : TBaseTest;
begin
a := CreateNewObject;
ShowMessage('The number is :' + IntToStr(a.GetValue));
a.Free;
end;


and for completeness, the DLL function declaration


function CreateNewObject : TBaseTest; stdcall; external 'MyDll.dll';


.Zanthos

Snape
08-11-2002, 04:35 PM
Thank you!

Useless Hacker
08-11-2002, 07:11 PM
I think the proper way is to use interfaces (like in DirectX). I think its basically as described above, but your class will look like this:
TMyClass = class(TObject, IMyInterface)

I have never used interfaces, though, so I don't really know anything about it except what I read in Delphi in a Nutshell.

Alimonster
08-11-2002, 10:22 PM
Yep, you can use interfaces in place of abstract virtual procedures. An interface is pretty much that - a guarantee of what procedures/functions will be available in any class implementing the interface. This lets you call a procedure on a class with a given interface - without worrying whether the classes are descended from a base class. E.g.

type
IMyInterface = Interface
procedure SomeProc;
end;

TClassOne = class(TInterfacedObject, IMyInterface)
public
procedure SomeProc;
end;

TClassTwo = class(SomeOtherClass, IMyInterface)
public
procedure SomeProc;
end;

procedure DoIt(const Thing: IMyInterface);
begin
Thing.SomeProc;
end;

In the above code, the two classes will implement the IMyInterface interface, which guarantees they'll have a given procedure ("SomeProc") with the correct signature. Of course, the procs can be implemented in any way that's fitting. Note that they're not necessarily inheriting the functionality from a base class - which means the interface "inheritance" goes across, rather than down the inheritance tree.

If you're using an interface, you add the interface in a class' declaration after the base class - you can implement many, which is another neat feature of interfaces ("TMyClass = class(TInterfacedObject, IOne, ITwo, IThree)").

Interfaces are reference counted. This means that they can die when out of scope, which is nice and handy. Any class implementing an interface, though, has to implement AddRef, Release and QueryInterface. These are responsible for the reference counting. If you don't want to bother with that, you can use TInterfacedObject (Delphi 6+) as a base class, which includes implementations for those functions.

Here's a quick example of how an interface could be used. It may be totally wrong, so caveat emptor (this is my first ever dll ;)):

DLL Code:
library dlltest;

{$R *.res}

uses
Dialogs;

type
IMyInterface = Interface
['{0806339A-4EDD-48A5-A60A-54E6E9AF94E1}']
procedure DoIt;
end;

type
TMyClass = class(TInterfacedObject, IMyInterface)
private
protected
public
destructor Destroy; override;
procedure DoIt;
end;

procedure TMyClass.DoIt;
begin
ShowMessage('Doing something');
end;

destructor TMyClass.Destroy;
begin
ShowMessage('I am dying!');
inherited;
end;

function GetObj: IMyInterface; stdcall;
begin
Result := TMyClass.Create;
end;

exports
GetObj;

begin
end.

Test code in unit using DLL (assumes a button placed on form):
unit dll_test_main;

interface

uses
Windows, Classes, Controls, Forms, StdCtrls;

type
IMyInterface = Interface
['{0806339A-4EDD-48A5-A60A-54E6E9AF94E1}']
procedure DoIt;
end;

TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

function GetObj: IMyInterface; stdcall; external 'dlltest.dll';

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
a: IMyInterface;
begin
a := GetObj;
a.DoIt;
end; // object dies here

end.
Notice that you don't need to create or free the class now! It's lifetime is managed by the interface - when the interface goes out of scope, it will kill the object (as can be seen in the destructor). That's a lot less hassle.


Meh. I'm bored, sigh. If I messed up any of the above, let me know please.

[Edit: Hmm, I should probably declare the exported func as stdcall so that other languages have an easier time. The change has been made!]

Snape
09-11-2002, 11:30 AM
Well, one last question: How to access variables? When I create a D3DDevice via the dll then it says it's nil to the application but it's not (little bit strange, but the Device was created).

Snape
09-11-2002, 11:46 AM
Okay, I've done a little workaround: I've just written a function which gives back the device.

thanks (I love this forums)

Alimonster
09-11-2002, 12:12 PM
The forums love you too, Snape! :P

A quick tip for interfaces: you can insert a GUID (globally unique ID) after the '= interface' bit using CTRL-SHIFT-G. That's what the scary long number is in the above code. You don't specifically need a GUID, but it can give you more options later in using the interface IIRC.