Last Updated:

Dynamic Link Libraries (DLL) | Delphi | Tutorial

Essentially, dynamic libraries differ from executable files only in one way, they cannot be run independently. In order for a dynamic library to start working, it is necessary that it be called by an already running program or a working DLL.

Dynamic Link Libraries (DLL)

 

Dynamic Link Libraries (DLLs) play an important role in the functioning of windows operating systems and application programs. They are files with compiled executable code that is used by applications and other DLLs. The implementation of many FUNCTIONS of the OS is placed in dynamic libraries, which are used as needed, thereby saving address space. A DLL is loaded into memory only when it is accessed by a process.

Typically, dynamic libraries contain groups of functions that are used to solve similar problems. In addition, they can store and use a variety of resources - from localization lines to forms.

A shared library can be used by multiple applications, and it is not necessary that they all be created using the same programming language.

A variation of dynamic libraries are Delphi packages, which are designed to store component code for the development environment and applications.

The use of dynamic libraries allows you to achieve a number of advantages:

  • The size of the application's executable file and the resources it occupies are reduced.
  • DLL functions can use multiple processes at the same time.
  • dynamic library management is entrusted to the operating system;
  • making changes to the DLL does not require recompilation of the entire project;
  • one DLL can be used by programs written in different languages.

When developing dynamic libraries in Delphi, it is convenient to use a group of projects that includes an application project and dynamic library projects.

This chapter covers the following topics:

  • DLL file structure
  • initialize the DLL;
  • explicit and implicit loading;
  • Calls to functions from the dynamic library
  • Resources in dynamic libraries

DLL Project

 

There is a special template in the Delphi Repository to create a dynamic link library. Its DLL Wizard icon is located on the New Repository page. Unlike a regular application project, a DLL project consists of only one source file. You can then add individual modules and forms to it.

Code Listing 28.1. The source file of the dynamic library project.

library Projectl;
{ Important note about DLL memory management:
 ShareMem must be the first unit in your library's USES clause AND your project's
 (select Project-View Source)
 USES clause if your DLL exports any procedures or functions that pass strings
 as parameters or function results.
 This applies to all strings passed to and from your DLL-even those that are nested
 in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL
shared memory manager, which must be deployed along with your DLL.
To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters. }
uses
SysUtils, Classes;
{$R *.res}
begin
end.

Note
An extensive comment in each DLL project relates to the use of the ShareMem module, which is described below
.

To determine the project type, use the library keyword (instead of program in a regular project). When you compile such a dynamic library project, a file with a .dll extension is created.

As with any other project, you can use other modules in a dynamic library project. These can be just source code modules and form modules. In this case, the dynamic library can export functions described not only in the main file, but also in attached modules.

Block begin.. end is called the library initialization block and is designed to host code that runs automatically when a DLL is loaded.

Between the uses section and the initialization block, you can place the source code of the dynamic library functions and their declarations. However, you can use any object Pascal construct, as well as use forms and components.

Note
When you create dynamic libraries, it is very convenient to use project groups. The application project and the project(s) of the dynamic link library(s) that it requires for its operation are placed in the group. To switch between projects, it is convenient to use the Project Manager (project manager command from the View menu). You can place it in the Code Editor window
.

Another way to conveniently work with dynamic link library projects is to specify the calling program for the DLL. This is done in the Parameters command dialog of the Run menu (Figure 28.1). The calling application is specified in the Host Application group. As a result, after the dynamic library is compiled, the application that uses it is called.

In order for applications to use dynamic link library functions, it is necessary, first, to export them from the DLL; second, declare the functions in the application itself as external. Here are ways to accomplish these tasks:

delphi - dll

Fig. 28.1. Parameters command dialog of the Run menu

 

Export from DLL

 

Use the exports keyword to create a list of procedures and functions that are exported from the dynamic link library. In this case, you can specify both the functions described in the main DLL file and the functions from the attached modules.

As an example, consider the source code of the DataCheck dynamic library, the simplest functions of which check the entered string before conversion for compliance with one of the data types.

Code Listing 28.2. The source code of the DataCheck dynamic library.

library DataChek
uses
Windows, SysUtils, Classes, Messages, Forms,
Dialogs, StdCTRLs, ComCTRLs;
function ValidDate(AText: String): Integer;
begin
try
Result: = 0; StrToDate(AText);
except
on E:EConvertError do Result: = -1;
end;
end;
function ValidTime(AText: String): Integer;
begin
try
Result: = 0; StrToTime(AText);
except
on E:EConvertError do Result: = -1;
end;
end;
function Validlnt(AText: String): Integer;
begin
try
Result: = 0; StrToInt(AText);
except
on E:EConvertError do Result: = -1;
end;
end;
exports Validlnt,
ValidDate index 1, ValidTime index 2 name 'IsValidTime';
begin
if Length(DateToStr(Date)) < 10
then ShowMessage('The year is represented by two digits');
end.

So, the three functions of this library ensure that a string is validated before converting it to an integer, date, or time. To enable the export of these functions, you must declare them in the exports section.

When you compile a library, the address, name, and sequence number of the exported function are added to a special export table in the DLL file.

Note
The Delphi compiler will easily add an export table to the application's executable file. However, it is not possible to access such a function because it is a Windows system limitation
.

Try declaring a couple of functions after the exports keyword in a normal application —the project compiles without errors. But the functions themselves are not available to other processes.

The names of the procedures and functions in the export section are separated by commas. A close look at the export example in Listing 28.2 reveals three different ad options.

In the first option, the compiler independently determines the position of the function in the export table.

When using the index keyword, the number following it sets the position of the function in the export table relative to other similar functions.

The name keyword allows you to export a function under a different name.

Call agreements

When declaring procedures and functions in dynamic libraries, different call conventions are used. The fact is that different programming languages implement the transfer of parameters to the procedure in different ways (through the stack or registers). The order of the parameters in the stack is determined by the call convention.

The standard call in C++ and Object Pascal is different, but a set of call type change directives allows you to provide any implementation.

In all call conventions, the calling procedure places parameters on the stack. Depending on the type of agreement, the stack is cleaned up by the calling or invoked procedure.

If the stack is cleaned up by the calling procedure, it has time to retrieve the return values from the caller.

If the stack is cleaned by the procedure being called, it first places the return values in the temporary memory area.

Note
In addition to the directives discussed below, there are three other types of calls that are not used and are retained for backward compatibility: these are the nearfarexport directives
.

Register Directive

This directive is used by default. Therefore, there is no need to add the register keywords after the function declaration. This type of call is called a fast call. It uses three extended registers of the processor, in which variables with a length of no more than 32 bits and pointers are placed. The remaining parameters are placed in the stack from left to right. Once used, the stack is cleared by the procedure being invoked.

Pascal Directive

Implements calls in the style of the Pascal language. The procedure that is called is responsible for clearing the stack. Settings are stacked from left to right. This method of calling is very fast, but does not support a variable number of parameters. Used for backward compatibility.

Stdcall Directive

Settings are stacked from left to right. The stack is cleaned by the procedure being called. This call handles a fixed number of parameters.

Cdecl Directive

Implements calls in the style of the C language. Parameters in the stack are placed from right to left. Clearing the stack is done by the calling procedure. Such calls provide service for a variable number of parameters, but the processing speed is less than in calls when implementing the pascal directive.

This directive is primarily used to refer to dynamic libraries that use C-style call conventions. Using the cdecl directive for Delphi libraries will not cause a compilation error, but a variable number of parameters will not.

Safecall Directive

Settings are stacked from right to left. The stack is cleaned by the procedure being called. It is used in COM and technologies based on it.

Initializing and Shutting Down a DLL

When the dynamic library is loaded, the initialization code is executed, which is located in the begin. end (see Listings 28.1 and 28.2). Typically, operations are performed here to set the initial values of variables used in the functions of the library, check the conditions for the functioning of the DLL, create the necessary structures and objects, and so on.

If you encounter initialization code execution errors, you can use the special exitcode global variable from the System module. If you assign any non-zero value to this variable during an exception, the library will be interrupted.

Note
Any global variables declared in the DLL are not available outside the DLL
.

It turns out that when you load a dynamic link library into the address space of the process that called it, important events occur, the knowledge of which will allow you to effectively manage the initialization and unloading of the DLL.

So, before the initialization code is run, the built-in assembly procedure _initDLL (it is located in the system module) is automatically called. It saves the state of the processor registers; Gets the value of the library module instance and writes it to the global variable hinstance. sets the global variable isLibrary to True (you can always recognize the DLL code from this value); Gets a number of parameters from the stack. checks a variable of the DLLProc procedural type:

var DLLProc: Pointer;

This variable is used to validate the operating system's calls to the DLL entry point. You can associate a procedure with a single integer parameter to this variable. This procedure is called a system-level callback function.

If the _initDLL procedure finds an associated callback function when checking the DLLProc variable, it is called. In this case, it is passed the parameter received from the stack. Four values can be passed as a parameter:

const
DLL_PROCESS_DETACH = 0;
DLL_PROCESS_ATTACH = 1;
DLL_THREAD_ATTACH = 2;
DLL_THREAD_DETACH = 3;

Let's take a look at them.

  • The value DLL_PROCESS_DETACH is passed when the DLL is unloaded from the process address space. This occurs when the FreeLibrary system function is called explicitly (see below) or when the process is terminated.
  • A value of DLL_PROCESS_ATTACH means that the library is displayed in the address space of the process that loads it for the first time.
  • The DLL_THREAD_ATTACH value is sent to all dynamic libraries loaded into the process when a new thread is created. Note that when you create a process and a primary thread, only one DLL_PROCESS_ATTACH value is sent.
  • The DLL_THREAD_DETACH value is sent to all dynamic libraries loaded into the process when an existing thread is destroyed.

Subsequently, when the process works with the loaded DLL, if the described events occur, the callback function is called again and again. In this case, one of the considered values is passed to it.

This is a good way to organize the processing necessary in each case in the dynamic library. How do I do this?

First, you must create a procedure that is appropriate for the DLLProc procedural type and write source code for it that is applied based on the parameter passed.

Second, in the initialization section, you must bind the DLLProc variable to the procedure that you created.

Call the DLL. Implicit challenge.

 

Now let's look at how functions are called from dynamic libraries.

When you run an application's executable file, the operating system creates a separate process for the application to run. The system also creates a primary thread owned by the process. The application process receives 4 GB of address space in which the executable code of the application is displayed.

After that, information about all dynamic libraries and their functions called by the application is extracted from the executable code. This information is based on the analysis of the source code by the Delphi linker, which includes the names of functions and dynamic libraries in the executable. This uses the implicit call described below.

As a result, when an application accesses a function from a DLL, all the information about it is already in the process. To execute the function (the call is made by one of the threads of the application process), the corresponding dynamic library is loaded into the address space of the application process. After that, the executable DLL code becomes fully accessible within the process, but not outside it. Other Processes can load the same library and use its image in their own address space. This is why multiple applications can use the same Shared Library at the same time.

Each thread has its own stack into which DLL function parameters and all necessary local variables are loaded. The fact is that dynamic libraries do not have their own heap and cannot own data. Therefore, any data or objects generated by DLL functions belong to the calling thread.

Dynamic library functions can be called in two ways, explicit and implicit. Let's take a look at them.

Implicit challenge

The mechanism of implicit invocation is the simplest because it is executed automatically and is based on the information available in the application about the functions and dynamic libraries that are called. However, the developer does not have the ability to influence the progress of the DLL load. If the operating system is unable to load the library, you simply receive an error message. The only way to influence the boot process is to use the library's initialization section (see above).

As an example of an implicit call, consider a simple DemoDLLl application that uses the functions of the DataCheck library (see above). To do this, it has three TEdit components that check the entered string against the format of one of the data types.

Note
The DemoDLL1 and DataCheck projects are grouped together. Switching between projects is easily done by the Project Manager utility
.

Code Listing 28.5: The main form module of the DemoDLL1 project.

unit Unitl;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
 Forms, Dialogs, StdCTRLs, comCTRLs, Buttons;
type
TMainForm = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
procedure EditlExit(Sender: TObject);
procedure Edit2Exit(Sender: TObject);
procedure EditSExit(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
function IsValidlnt(AText: String): Boolean; external 'DataCheck.dll';
function IsValidDate(AText: String): Boolean; external 'DataCheck.dll';
function ValidTime(AText: String): Boolean; external 'DataCheck.dll';
implementation {$R *.DFM}
procedure TMainForm.EditlExit(Sender: TObject);
begin if not IsValidlnt(Editl.Text)
then Editl.Clear;
end;
procedure TMainForm.Edit2Exit(Sender: TObject);
begin
if not IsValidDate(Edit2.Text)
then Edit2.Clear; end;
procedure TMainForm.Edit3Exit(Sender: TObject);
begin if not ValidTime(Edits.Text)
then EditS.Clear;
end;
end.

To make an implicit call, simply declare the desired function with an external directive and specify the name of the dynamic library that contains it. Note that the third function is declared under the alias isValidTime, which is declared for this function using the name keyword in the source code of the dynamic library.

In the future, the imported functions are used in the usual way.

Explicit challenge

An explicit call to a dynamic link library involves the programmer creating the appropriate source code. It needs to provide for loading the DLL, obtaining the addresses of variables of the procedural type for the functions and procedures used, and unloading the DLL.

An example of an explicit call to dynamic link library functions is available in the DemoDLL2 demo application, which is exactly the same as the previous example.

Code Listing 28.6. The main form module of the DemoDll2 project.

unit Unit2;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCTRLs;
type
StandardProc = function(AText: String): Boolean;
TMainForm = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
procedure FormShow(Sender: TObject);
procedure EditlExit(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Edit2Exit(Sender: TObject);
procedure EditSExit(Sender: TObject);
private
DLLHandle: THandle;
LoadError: Word;
IsValidlnt: StandardProc;
IsValidDate: StandardProc;
ValidTime: StandardProc;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation {$R *.DFM}
procedure TMainForm.FormShow(Sender: TObject);
begin
DLLHandle: = LoadLibrary('DataCheck');
if DLLHandle = 0 then begin if GetLastError = ERROR_DLL_NOT_FOUND
then ShowMessagef'loading error DLL');
Close;
end;
@IsValidInt: = GetProcAddress(DLLHandle, 'IsValidlnt');
SIsValidDate: = GetProcAddress(DLLHandle, 'IsValidDate');
SValidTime: = GetProcAddress(DLLHandle, 'ValidTime');
end;
procedure TMainForm.FormClose(Sender: TObject;
var Action: TCloseAction);
begin if DLLHandle <> 0
then FreeLibrary(DLLHandle);
end;
procedure TMainForm.EditlExit(Sender: TObject);
begin
if not IsValidlnt(Editl.Text)
then Edit2.Clear;
end;
procedure TMainForm.Edit2Exit(Sender: TObject);
begin if not IsValidDate(Edit2.Text)
then Editl.Clear;
end;
procedure TMainForm.EditSExit(Sender: TObject);
begin
if not ValidTime(Edit3.Text)
then Edit3.Clear;
end;
end.

Loading the DataCheck dynamic library is done in the FormShow handler method using the LoadLibrary function. The dynamic link library name may not contain a route if the DLL file is located in the same directory as the program. If no DLL file is found in this directory, the search is performed sequentially on the current directory, \SYSTEM, and directories in the Path list.

Since there is no exception for this system function, possible errors are monitored. The GetLastError function returns the last error code.

Note
The error code ERROR_DLL_NOT_FOUND, along with many other codes, is contained in the Windows.PAS file
.

If the library is successfully loaded, the addresses of the corresponding DLL functions are passed to the three procedural variables of type standardProc. The standardProc procedural type is declared before the form class. To do this, use the GetProcAddress system function.

In the future, the functions created in this way are applied to the input values in the TEdit components.

When you close the application, you must unload all the dynamic libraries in use using the FreeLibrary system function.

Resources in DLLs

Dynamic libraries can contain not only executable code that performs some calculations, but also resources. Most often, it is necessary to distribute forms that enable procedures and functions to work together with DLLs. The techniques for working with forms in dynamic library projects are no different from those in regular application projects.

The only thing is that any form in a DLL should be treated as being created manually, not automatically. In this case, a pointer to the owner of the future form must be passed to the procedure that creates the form.

For example, the showDemoForm procedure from the DataCheck library we are looking at looks like this:

procedure ShowDemoForm(AOwner: TComponent);
begin
DemoForm: = TDemoForm.Create(AOwner);
DemoForm.ShowModal;
DemoForm.Free;
end;

The destruction of a form can be organized not only in the procedure itself, but also (if repeatedly used) in another procedure or when the dynamic link library is unloaded.

When you call this procedure from an application, you must specify an instance of the application's class in the parameter:

procedure ShowDemoForm(AOwner: TComponent); external 'DataCTRL.dll';
procedure TMainForm.BitBtnlClick(Sender: TObject);
begin
ShowDemoForm(Application);
end;

Note that in this case, the form from the dynamic link library is treated by the operating system as a separate task, as evidenced by the system taskbar.

To distribute with an application, you can create special resource dynamic libraries that are used to localize applications. For example, you can put all string constants (messages, texts, etc.) in the resource library, and with the application you can distribute a dynamic resource library, the strings in which correspond to the customer's language requests.

You can create such a library by using the Delphi Repository (New page) for an application project or dynamic library. The New Resource Library Wizard walks you through all the steps of creating a library project.

Note
For each language, you must create your own form variations and a new resource library project
.

Before you can create a resource library project, you must save and compile the base project (a localization project is created for it), and then start a new resource library project.

The first dialog of the Resource Library Wizard provides reference information.

The second allows you to create a list of forms of the basic project that will be included in the library. You can remove forms from the list and add the forms that you want from other projects.