Creating and Using DLLs in Delphi - ablealias/Delphi GitHub Wiki
A Dynamic Link library
, or DLL, is a collection of routines (small programs) that can be called by applications
and by other DLLs. Like units, DLLs contain sharable code or resources, they provide the ability for multiple
applications to share a single copy of a routine they have in common. The concept of DLLs is the core of the
Windows architectural design, and for the most part Windows is simply a collection of DLLs. Naturally, using
Delphi, we can write and use our own DLLs, and we can call functions in DLLs developed with other systems /
by other developers (like Visual Basic, or C/C++).
Creating a Dynamic Link Library
The following few lines will demonstrate how to create a simple DLL using Delphi. For the beginning start Delphi and select File | New ... DLL. This will create a new DLL template in the editor window. Select the default text and replace it with the next piece of code.
library TestLibrary;
uses SysUtils, Classes, Dialogs;
procedure DllMessage; export;
begin
ShowMessage('Hello world from a Delphi DLL') ;
end;
exports DllMessage;
begin
end.
If you look at the project file of any Delphi application, you’ll see that it starts with the reserved word
Program
. By contrast, DLLs always begin with the reserved word Library
. This is then followed by a uses
clause for any needed units. In this simple example, there then follows a procedure called DllMessage which
does nothing except showing a simple message. At the end of the source code we find an exports
statement.
This lists the routines that are actually exported from the DLL in a way that they can be called by another
application. What this means is that we can have, let's say, 5 procedures in a DLL and only 2 of them (listed in
the exports section) can be called from an external program (the remaining 3 are "sub procedures" in a DLL).
To import a procedure contained in a DLL, we use the keyword external
in the procedure declaration. For
example, given the DllMessage procedure shown earlier, the declaration in the calling application would look
like :
procedure DllMessage; external 'SimpleMessageDLL.dll'
the actual call to a procedure will be nothing more than `DllMessage;'
The entire code for a Delphi form (name: Form1) with a TButton on it (name: Button1) that's calls the
DLLMessage function could be:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes,
Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject) ;
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
procedure DllMessage; external 'SimpleMessageDLL.dll'
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject) ;
begin
DllMessage;
end;
end.
Dynamically Loading DLL's
There are various reasons for using DLL's this way, with the main cost being that of additional coding. By
dynamically loading a DLL you decide at runtime which DLL to use
. This means you can give your program
different functionality depending on which DLL's are present (freeware or shareware versions of a program). A
use I have found for this in the past is to include each report that is required by a program in a different DLL
and then scan a directory and build up a list of the available reports. This makes adding and modifying reports
very easy. This tutorial will use the DLL created in Adding forms to a DLL, this DLL exports one function and
one procedure which were declared like:
procedure ShowDllForm;stdcall;
function ShowDllFormModal:integer;stdcall;
In order to call these from our application we need to declare two new types which correspond to those in the
DLL, these are declared like this:
TShowForm = procedure;stdcall;
TShowFormModal = function :integer;stdcall;
We now need to create an instance of these two types, which we will do in the private part of our main class.
ShowForm : TShowForm;
ShowFormModal : TShowFormModal;
At present a call to either of these functions would result in an access violation as they do not actually point
to anything. The next step therefore is to load the DLL and make our two functions point to the corresponding
ones in the DLL, this is done like so
DLLHandle := LoadLibrary('Project1dll.dll');
if DLLHandle <> 0 then
begin
@ShowForm := GetProcAddress(DLLHandle, 'ShowDllForm');
@ShowFormModal := GetProcAddress(DLLHandle,
'ShowDllFormModal');
end;
All we are doing here is loading the dll into memory (LoadLibrary
) and then getting the address
(GetProcAddress
) of the two functions that we wish to call. We can now call ShowForm and ShowFormModal as
if they were standard procedures.
When we have finished using the DLL we need to remove it from memory, this is done like:
FreeLibrary(DLLHandle);
When the DLL is unloaded you must make sure that you never try to call any of the functions contained in it,
otherwise you'll be greeted with an access violation.
Static vs. Dynamic Dynamic Link Library Loading - A Comparison
should you use static or dynamic DLL loading?.
Before you can call routines defined in DLL, you must import them. Functions exported from a DLL can be
imported in two ways: by declaring an external procedure
or function (static)
, or by direct calls to DLL
specific API functions (dynamic). Let's create a simple DLL. Here's the code to the "circle.dll" exporting one
function "CircleArea" which calculates the area of a circle using the given radius:
library circle;
uses
SysUtils, Classes, Math;
{$R *.res}
function CircleArea(const radius : double) : double; stdcall;
begin
result := radius * radius * PI;
end;
exports CircleArea;
begin
end.
Static Loading
The simplest way to import a procedure or function is to declare it using the external directive: function CircleArea(const radius : double) : double; stdcall external 'circle.dll'; If you include this declaration in the interface part of a unit, circle.dll is loaded once, when the program starts. Throughout execution of the program, the function CircleArea is available to all units that use the unit where the above declaration is.
Dynamic Loading
You can access routines in a library through direct calls to Win32 APIs, including LoadLibrary
, FreeLibrary
,
and GetProcAddress
. These functions are declared in Windows.pas
.
Here's how to call the CircleArea function using dynamic loading:
type
TCircleAreaFunc = function (const radius: double) : double; stdcall;
var
dllHandle : cardinal;
circleAreaFunc : TCircleAreaFunc;
begin
dllHandle := LoadLibrary('circle.dll') ;
if dllHandle <> 0 then
begin
@circleAreaFunc := GetProcAddress(dllHandle, 'CircleArea') ;
if Assigned (circleAreaFunc) then
circleAreaFunc(15) ; //call the function
else
ShowMessage('"CircleArea" function not found') ;
FreeLibrary(dllHandle) ;
end
else
begin
ShowMessage('circle.dll not found / not loaded') ;
end;
end;
When you import using dynamic loading, the DLL is not loaded until the call to LoadLibrary
. The library is
unloaded by the call to FreeLibrary
.
With static loading the DLL will be loaded and its initialization sections will execute before the calling
application's initialization sections are executed. With dynamic loading, this is reversed.
Static or Dynamic PROS & CONS
Static loading PROS:
- More easy for a beginner developer, no "ugly" API calls.
- DLLs loaded once, when the program starts.
Static loading CONS:
- The application will NOT start if any DLLs are missing (cannot be found). When you run the program you will see an ugly message: "This application has failed to start because 'missing.dll' was not found. Re-installing the application may fix this problem".
- More memory used, as all DLLs are loaded even if you will not use some of the functions.
By design the DLL
search order with static linking includes: the directory from which the application loaded, the system directory, the Windows directory, directories listed in the PATH environment variable.
Note also that the search order might be different for various Windows versions. The safest is to always expect to have all the DLLs in the directory where the calling application is. Dynamic loading PROS: - You can run your program even when some of the libraries it uses are not present.
- Smaller memory consumption - DLLs used when needed.
- You can specify the full path to the DLL.
- Use for functionality that is rarely needed by the application.
- Could be used for modular applications. The application only exposes (loads) modules (dlls) "approved" for the user.