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.