Computing: Free Pascal Programming

Building and using Free Pascal dynamic link libraries.

A shared library is a computer file that contains executable code designed to be used by multiple computer programs or other libraries. When running a program, that is configured to use a shared library, the operating system loads the shared library from a file (other than the program's executable file) into memory at runtime.

A dynamic-link library (DLL) is a shared library in the Microsoft Windows or OS/2 operating system. It can contain executable code (functions, procedures), data, and resources, in any combination. A DLL file mostly has the file extension .dll.

This tutorial is about creating a DLL using Lazarus/Free Pascal on Windows 10, and calling the DLL procedures from Pascal, C, and C++. It should apply to Windows 11, too, probably also to Windows 8, or even earlier. No idea, how far it applies to Linux or MacOS... The sample code has been tested using Lazarus 2.2.6 (with FPC 3.2.2 64-bit), and (for the C and C++ samples) MSYS2 2024-01-13, UCRT64 subsystem (with gcc 14.1.0 64-bit). The Pascal builds should work with other versions of FPC, too; no guarantee for other C/C++ compilers. For help with MSYS2, please, have a look at Using Sublime Text as IDE for GCC/GFortran development.

To create a shared library in Lazarus, choose File > New... from the menu bar, then, in the opening New... window, select Library

Free Pascal dynamic link libraries: Creating a new 'Library' project in Lazarus

This creates a source skeleton, similar to the one of a command line program. Note, that for a library, the reserved word program has to be replaced by the reserved word library

    library Project1;
    {$mode objfpc} {$H+}
    uses
        Classes;
    begin
    end.

Here are some general remarks concerning the code of a Free Pascal library.

Creating and building the library.

The sample library "display", used in this tutorial, contains some display procedures, based on the Crt unit. Most of them are not really useful for being called from a Pascal program (as the procedure itself, or a similar procedure already exists in the Crt unit), but they are a simple way to perform positional and colored output to the command line screen from a C/C++ program.

Here is the code of my "display" library (you are free to extend it with further procedures, or change it to fit your needs and wishes). The code includes a short description of what a given procedure does, and what are the arguments that you must specify when calling it. Click the following link to download the source code of the tutorial samples.

    library display;
    {$mode objfpc}{$H+}
    uses
        SysUtils, Crt, Strings, ctypes;

    { Clear the screen }
    procedure ClearScreen; cdecl;
    // Arguments: none
    begin
        ClrScr;
    end;

    { Clear end of line }
    procedure ClearEol; cdecl;
    // Arguments: none
    begin
        ClrEol;
    end;

    { Display end of line character = new line }
    procedure WriteEol; cdecl;
    // Arguments: none
    begin
        Write(LineEnding);
    end;

    { Repeated character display }
    procedure WriteChar(C: Char; Count: cint32); cdecl;
    // Arguments:
    //   character to be displayed (Char)
    //   repeat factor = count (CInt32)

    var
        I: Integer;
    begin
        for I := 1 to Count do
            Write(C);
    end;

    { Formattded string display }
    procedure WriteString(S: PChar; Align: Char = 'L'; Width: cint32 = 0); cdecl;
    // Arguments:
    //   string to be displayed (PChar)
    //   alignment (Char): L (left; default), R (right), C (center)
    //   display area width (CInt32); left/right/left+right padding with spaces; default = 0 (no padding)

    var
        Len, Spaces, Spaces2: Integer;
    begin
        Align := Uppercase(Align)[1];
        Len := StrLen(S);
        if Width >= 0 then begin
            Spaces := Width - Len;
            if Align = 'L' then begin
                // Left alignment
                Write(S);
                WriteChar(' ', Spaces);
            end
            else if Align = 'R' then begin
                // Right alignment
                WriteChar(' ', Spaces);
                Write(S);
            end
            else if Align = 'C' then begin
                // Centered
                Spaces2 := Spaces div 2;
                WriteChar(' ', Spaces2);
                Write(S);
                WriteChar(' ', Spaces2);
                if Spaces mod 2 = 1 then
                    Write(' ');
            end;
        end;
    end;

    { Formatted integer display }
    procedure WriteInt(N: cint32; Width: cint32 = 0); cdecl;
    // Arguments:
    //   integer to be displayed (CInt32)
    //   display area width (CInt32); left padding with spaces; default = 0 (no padding)

    begin
        if Width >= 0 then
            Write(N:Width);
    end;

    { Formatted float (real) display }
    procedure WriteFloat(R: cfloat; Width: cint32 = 0; Digits: cint32 = 0); cdecl;
    // Arguments:
    //   float to be displayed (CFloat)
    //   display area width (CInt32); left padding with spaces; default = 0 (no padding)
    //   number of decimal digits (CInt32); default = 0

    begin
        if (Width >= 0) and (Digits >= 0) then
            Write(R:Width:Digits);
    end;

    { Set foreground (text) and background colors }
    procedure SetColor(FG: cint32; BG: cint32 = Black); cdecl;
    // Arguments:
    //   foreground color (CInt32); Crt unit text color (0 - 15)
    //   background color (CInt32); Crt unit background color (0 - 7); default = 0 (black)

    begin
        if (FG >= 0) and (FG <= 15) and (BG >= 0) and (BG <= 7) then begin
            TextColor(FG); TextBackground(BG);
        end;
    end;

    { Reset foreground (text) and background colors }
    procedure ResetColor; cdecl;
    // Arguments: none
    // Text color is set to LightGray (7), background color to Black (0)

    begin
    TextColor(LightGray); TextBackground(Black);
    end;

    { Colored string display }
    procedure WriteColor(S: PChar; FG: cint32; BG: cint32 = Black); cdecl;
    // Arguments:
    //   string to be displayed (PChar)
    //   foreground color (CInt32); Crt unit text color (0 - 15)
    //   background color (CInt32); Crt unit background color (0 - 7); default = 0 (black)

    begin
        SetColor(FG, BG);
        Write(S);
        ResetColor;
    end;

    { Set cursor position }
    procedure SetPos(Y, X: cint32); cdecl;
    // Arguments:
    //   screen row = Y (CInt32)
    //   screen column = X (CInt32)

    begin
        if (X > 0) and (Y > 0) then
            GotoXY(X, Y);
    end;

    { Positional string display }
    procedure WritePos(S: PChar; Y, X: cint32); cdecl;
    // Arguments:
    //   string to be displayed (PChar)
    //   screen position: row = Y (CInt32)
    //   screen position: column = X (CInt32)

    begin
        SetPos(Y, X);
        Write(S);
    end;

    { Export the procedures (!) }
    exports
        ClearScreen, ClearEol, WriteEol, WriteChar, WriteString, WriteInt, WriteFloat,
        SetColor, ResetColor, WriteColor, SetPos, WritePos;

    end.

Build the DLL in Lazarus, just the same way that you would build a program (choose Run > Build from the menu bar). The screenshot shows a successful build with the creation of a DLL (display.dll).

Free Pascal dynamic link libraries: Building a 'Library' project in Lazarus

Testing the DLL procedures (Pascal).

Some general remarks concerning the code of a Free Pascal program calling DLL subroutines.

Here is the code of a simple program (test_display) that can be used to test (most of) the procedures of the "display" DLL.

    program test_display;
    {$mode objfpc}{$H+}
    {$LINKLIB display}
    uses
        ctypes;
    const
        Black = 0; Yellow = 14; DarkGray = 8;
        Title: PChar = 'Test DISPLAY unit.';
        EndOfProg: PChar = 'Enter to terminate ';
        Name: PChar = 'Aly';
        Arr: array[0..9] of Real = (
            1, 1.25, 1.50, 2, 2.25, 2.50, 3, 3.25, 3.50, 4
        );
    var
        I: Integer;

    { Declaration of the external (DLL) procedures used }
    procedure ClearScreen; cdecl; external;
    procedure WriteEol; cdecl; external;
    procedure SetColor(FG: cint32; BG: cint32 = Black); cdecl; external;
    procedure ResetColor; cdecl; external;
    procedure SetPos(Y, X: cint32); cdecl; external;
    procedure WriteString(S: PChar; Align: Char = 'l'; Width: cint32 = 0); cdecl; external;
    procedure WriteChar(C: Char; Count: cint32); cdecl; external;
    procedure WriteFloat(R: cfloat; Width: cint32 = 0; Digits: cint32 = 0); cdecl; external;
    procedure WriteColor(S: PChar; FG: cint32; BG: cint32 = Black); cdecl; external;
    procedure WritePos(S: PChar; Y, X: cint32); cdecl; external;

    { Main program }
    begin
        ClearScreen;
        // Display colored text (underlined with '=' signs) at given screen position
        SetColor(Yellow);
        WritePos(Title, 1, 21);
        SetPos(2, 21);
        WriteChar('=', 18);
        ResetColor; WriteEol; WriteEol;
        // String alignment (left, right, centered)
        WriteChar('-', 10); WriteEol;
        WriteString(Name); WriteEol;
        WriteString(Name, 'r', 10); WriteEol;
        WriteString(Name, 'c', 10); WriteEol; WriteEol;
        // Formatted display of real numbers
        for I := 0 to 9 do begin
            WriteFloat(Arr[I], 0, 2);
            WriteChar(' ', 2);
        end;
        WriteEol; WriteEol;
        // Colored string display
        WriteColor(EndOfProg, DarkGray);
        // Wait for user hitting ENTER key (and terminate the program)
        Readln;
    end.

To build the program in Lazarus, place the DLL into the project directory, and build the program just the same way that you always do. As the custom display functions are part of display.dll, thus, to execute the program, you must place the DLL where Windows can find it, i.e. either into the same directory as the executable, or into C:\Windows\System32. Trying to run the program without the DLL results in its abortion with the error message Entry point not found, similar to the one on the screenshot.

Free Pascal dynamic link libraries: Program execution - Failure (DLL not found)

The screenshot below shows the successful execution of the program.

Free Pascal dynamic link libraries: Program execution - Success

Testing the DLL procedures (C).

Now lets test our DLL with a C program. The executable test_display1.exe should do the same as does the Pascal program test_display.exe from before.

The best practice is to define all functions and procedures of the DLL in a header file, where they have to be declared as extern. Here is the code of display.h.

    extern void ClearScreen(void);
    extern void ClearEol(void);
    extern void WriteEol(void);
    extern void SetColor(int, int);
    extern void ResetColor(void);
    extern void SetPos(int, int);
    extern void WriteString(char*, char, int);
    extern void WriteChar(char, int);
    extern void WriteFloat(float, int, int);
    extern void WriteColor(char*, int, int);
    extern void WritePos(char*, int, int);

With the procedures declared in display.h, all we have to do in the C source file is to include the header file. So no difference of any kind with the source of a "normal" C program. Here is the code of test_display.c.

    #include <stdio.h>
    #include "display.h"
    int main() {
        int Black = 0; int Yellow = 14; int DarkGray = 8;
        char *Title = "Test DISPLAY unit.";
        char *EndOfProg = "Enter to terminate ";
        char *Name = "Aly";
        float Arr[10] = {
            1, 1.25, 1.50, 2, 2.25, 2.50, 3, 3.25, 3.50, 4
        };
        char Buffer[2];
        ClearScreen();
        SetColor(Yellow, Black);
        WritePos(Title, 1, 21);
        SetPos(2, 21);
        WriteChar('=', 18);
        ResetColor(); WriteEol(); WriteEol();
        WriteChar('-', 10); WriteEol();
        WriteString(Name, 'l', 0); WriteEol();
        WriteString(Name, 'r', 10); WriteEol();
        WriteString(Name, 'c', 10); WriteEol(); WriteEol();
        for (int I = 0; I < 10; I++) {
            WriteFloat(Arr[I], 0, 2);
            WriteChar(' ', 2);
        }
        WriteEol(); WriteEol();
        WriteColor(EndOfProg, DarkGray, Black); fgets(Buffer, 2, stdin);
    }

To build the C program, the DLL has to be together with the source and the header file. I did this build in the MSYS2 UCRT64 shell, installed as terminal in Sublime Text (screenshot below). The build command is as follows:

    gcc -o test_display1.exe test_display.c display.dll

Free Pascal dynamic link libraries: Test DLL procedures with C program - Build

And here is the program output in UCRT64 (if the colors look somewhat different as before, this is due to the terminal, not to C; running the program in Command Prompt will produce exactly the same output as does the Pascal program).

Free Pascal dynamic link libraries: Test DLL procedures with C program - Execution

Testing the DLL procedures (C++).

We can use the C header file display.h from before. All we have to do in the C++ source file is to include the header file. However, as the procedures actually are C and not C++ code, the include has to be done using the extern "C" directive. For the rest, no difference of any kind with the source of a "normal" C++ program. Here is the code of test_display.cpp.

    #include <iostream>
    extern "C" {
        #include "display.h"
    }
    using namespace std;
    int main(void) {
        int Black = 0; int Yellow = 14; int DarkGray = 8;
        char *Title = "Test DISPLAY unit.";
        char *EndOfProg = "Enter to terminate ";
        char *Name = "Aly";
        float Arr[10] = {
            1, 1.25, 1.50, 2, 2.25, 2.50, 3, 3.25, 3.50, 4
        };
        char Buffer[2];
        ClearScreen();
        SetColor(Yellow, Black);
        WritePos(Title, 1, 21);
        SetPos(2, 21);
        WriteChar('=', 18);
        ResetColor(); WriteEol(); WriteEol();
        WriteChar('-', 10); WriteEol();
        WriteString(Name, 'l', 0); WriteEol();
        WriteString(Name, 'r', 10); WriteEol();
        WriteString(Name, 'c', 10); WriteEol(); WriteEol();
        for (int I = 0; I < 10; I++) {
            WriteFloat(Arr[I], 0, 2);
            WriteChar(' ', 2);
        }
        WriteEol(); WriteEol();
        WriteColor(EndOfProg, DarkGray, Black); fgets(Buffer, 2, stdin);
        return EXIT_SUCCESS;
    }

To build the C++ program, the DLL has to be together with the source and the C header file. I did this build in the MSYS2 UCRT64 shell, installed as terminal in Sublime Text (screenshot below). The build command is as follows:

    g++ -o test_display2.exe test_display.cpp display.dll

Free Pascal dynamic link libraries: Test DLL procedures with C++ program - Build

I'm not a C/C++ programmer, so I don't know if using code forbidden by ISO C++ is an issue. It's surely not in this sample, and the important thing for me is that the build succeeded without errors and that the executable test_display2.exe was created.

The program output of test_display2.exe is (of course) exactly the same as for the C program test_display1.exe.


If you find this text helpful, please, support me and this website by signing my guestbook.