Introduction to using DLLs in Delphi

posted 15 Mar 2010, 12:02 by Delphi Basics
A DLL consists of two basic parts, the library source file which describes the code for the DLL and an interface unit, which provides the interface between a user program and the DLL. The outline structure of the DLL source file is as follows (Delphi keywords are in uppercase...)

The following defines the main file of the DLL which can incorporate other files in the uses clause...Note that the interface and implementation keywords are absent which is one difference from the main dll unit and a standard unit in Delphi. The keyword LIBRARY replaces 'UNIT' followed by the name of the DLL and then the $DEFINE statement in curly braces is followed once again by the name of the DLL.  The STDCALL keyword describes a function that uses standard calls to its address (compatible with Windows.) Immediately after the uses clause the functions in the main dll unit are described, followed by the EXPORTS keyword and a list of those functions (or procedures) being exported.

LIBRARY dll_name;
{$DEFINE DLL_NAME}

USES
thisunit, thatunit, dll_interface;

FUNCTION some_dll_function(somevar : integer):integer; STDCALL;
...function code is here...
end;

{export the function name }

EXPORTS
some_dll_function;
END.


The interface unit is included in a program that wishes to call the DLL (by listing it in the uses clause of the unit). This is followed by the typical interface, which may include a type statement (of structures and records used by the DLL, providing an interface to these records for the calling program.) Next there is the function header declarations, wrapped around by the $IFNDEF keyword and concluded by $ENDIF.  This is followed by the implementation keyword, and then then, rather than having the actual code defined here, as in a typical unit, once again the $IFNDEF keyword is uses (using curly braces you might notice) instead we find next the dll name preceded by the EXTERNAL keyword. If the imported function is going to be named something other than the original name of the function then this is followed by the keyword NAME followed by the function(s) being imported, with the list concluded once again by the $ENDIF directive for the compiler, and then the end of the unit. I have found that if the name of the function is going to be the same then using the 'Name' keyword causes an error. For example let us suppose that a function 'some_dll_function' is imported with the same name as in the DLL. Using 'NAME' and then giving the name of the function generates a compiler error. If the function is imported using a different name ('a_dll_function' for example) then the return type must be included (for example, integer) or this will generate a compiler error, and then the NAME parameter must indicate the original name of the function within the DLL.

This file corresponds to the header files in C++ (files ending with the extension 'h') and the former file corresponds to the source file of the same name (ending with the extension 'cpp'). A Delphi user would have to convert a C++ header file to be able to use a DLL written in C++, while a C++ user would have to translate the Delphi interface file (as below) to use a DLL written in Delphi (so in otherwords, provided that you have the DLL the only sources that need translation are the interface files to the DLL, since compiled DLLs are executables, and thus language independent.

UNIT dll_interface_name;

INTERFACE
TYPE
{may include types used by the DLL which will also be accessible to the calling
program}

{$IFNDEF DLL_NAME}
{list the functions that were declared in the DLL after the export keyword}

FUNCTION some_dll_function(somevar : integer):integer; STDCALL;
{$ENDIF}

IMPLEMENTATION

{$IFNDEF DLL_NAME}
function some_dll_function; EXTERNAL 'DLL_NAME'; {NAME
'some_dll_function'}
{$ENDIF}

end.


Next, a program will wish to use the DLL, and so the caller includes the interface unit name in the uses declaration of their source code. This is referred to as 'implicitly loading a DLL'. As an alternative you can include the following line of code in the implementation section of your unit...

Function some_dll_function(somevar : integer):integer; STDCALL EXTERNAL
'dll_name';

In this case rather than writing the code for the function in the implementation section you would indicate that the code is external (resides in a DLL). In this case the interface unit is not declared in the uses clause. However this would not give you any access to the types declared in the interface (since  types and record structures are normally found in C++ header files and in the corresponding Delphi interface files).

As an alternative to implicit loading , a DLL can be explicitly loaded. Every time an application runs it loads those DLLs which are said to be loaded 'implicitly'.  However it may be the case that during a program run, a DLL is rarely likely to be used, and thus loading the DLL with the program would not be required. For example there might be some obscure task, that is rarely done, that resides in a DLL. In these cases a DLL could be explicitly loaded when required, and then discarded, which reduces load time and also frees up memory that would be otherwise wasted by loading in DLLs that are almost never used.

This is done by first declaring a procedural variable that mimics the definition of the DLL and an exception class to catch a failed load of the DLL (this is done in the type section of the unit).

Type
Tsome_dll_function(somevar : integer):integer; STDCALL;

EDLLLoadError = class(exception);


Next, in a procedure or function that will actually use the DLL the DLL will be
explicitly loaded, with the error trapping wrapped in a 'try ... except ' structure...

Var
  dllHandle : Thandle;
  Some_dll_function : Tsome_dll_function;
  x : integer;
begin
  DLLHandle := LoadLibrary(dll_name);
  Try {trap load error when the handle is zero}
   If DLLhandle = 0 then 
    Raise EDLLLoadError.Create('DLL load failure');
   @some_dll_function := GetProcAddress(DLLHandle, 'some_dll_function');
   If not (@some_dll_function = nil) then {use the dll function}
    X := some_dll_function(somevar) {here we are assuming somevar is passed returning an integer}
   Else
    RaiseLastWin32Error;
  Finally
   FreeLibrary(DLLHandle); {this unloads the dll and frees memory}
  End;
end;



First the LoadLibrary function is used to return a handle to the DLL being loaded, the name of which is passed as the parameter of the function. The next segment of code is preceded by the error catching 'try' statement. If the handle is returned as zero the CLL load failed, and an exception is raised and the code jumps out of the try segment of the code. Otherwise, code execution continues on the next line, by assigning as the address of the procedural variable the address of the function being used in the DLL, passing first the handle to the DLL and then the name of the function within the DLL for which the user wishes to find the starting address. If this address is not returned as nil (the function address exists) the user then proceeds to use the function in the DLL, otherwise an error is raised. Assuming the DLL has been used then the FreeLibrary function is called to release (unload) the DLL. As you can see all this is a little more complicated than implicitly calling a DLL (thus making the program responsible for loading the DLLs at startup and reporting any errors) but this does allow one to load DLLs on demand where this is required.

Using Delphi 5 (and I assume the earlier versions) you can begin to create a DLL by choosing 'New' from the Menu and then selecting DLL. There is a message about memory management that comes up and warns that you cannot pass strings as parameters (even in records) without including this memory manager. The way around this is to pass Pchars instead of strings. For example if your string is named Astring you can translate this to a Pchar by using the Pchar function in Delphi, thus avoiding the memory manager problem. For example to pass a string to a DLL :
some_dll_function(Pchar(Astring));

As an example of a very simple DLL here is the source code for a DLL that adds two integers...After choosing New on the Delphi file menu and then selecting DLL from the selection window, substitute the following code in the editor...

library addtwoints;
{$DEFINE addtwoints}

uses
SysUtils,
Classes,
addinteger in 'addinteger.pas'; {note - in this example no types are declared in
addinteger}

{$R *.RES}

function addint(x, y : integer):integer; STDCALL;
begin
Result := x + y;
end;

EXPORTS
addint;
end.

This concludes the DLL file.
Next choose 'New' and then 'Unit' to create the interface.

unit addinteger;

interface
{$IFNDEF addtwoints}
function addint(x, y : integer):integer; STDCALL;
{$ENDIF}


implementation

{$IFNDEF addtwoints}
function addint; stdcall; external 'addtwoints';
{$ENDIF}

end.

On the Delphi menu choose Project and then Build to build the DLL. (Note that this simple DLL compiles on my machine at around 50 KB).

Next start a new project (new application) and drop three edit boxes and a button on a form. Integers are entered into Edit1 and Edit2 and when the button is pushed the DLL code is called and adds the two integers returning the result which is then displayed in Edit box 3. Note that all that is required to use the DLL (with implicit loading) is mention of 'addinteger' the interface file in the uses clause of the unit.

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, addinteger;

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

var
Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin
  Edit3.text := inttostr(addint(strtoint(edit1.text) ,
  strtoint(edit2.text)));
end;

end.


Comments