macOS Dynamic Libraries

From Lazarus wiki

Overview

There are two important factors which determine the performance of applications: their launch times and their memory footprints. Reducing the size of an executable file and minimizing its memory use once launched make an application launch faster and use less memory. Using dynamic libraries instead of static libraries reduces the executable file size of an application. Dynamic libraries also allow applications to delay loading libraries with special functionality until they’re needed instead of loading them at launch time. This feature contributes further to reduced launch times and efficient memory use.

Static vs Dynamic

Note-icon.png

Note: Dynamic libraries are also known as dynamic shared libraries, shared objects, or dynamically linked libraries.
Static libraries are also known as static archive libraries and static linked shared libraries.

Most of an application's functionality is implemented in libraries of executable code. When an application's source code is compiled into object code and linked with a static library, the object code and library code that the application uses is copied into the executable file that is loaded into memory in its entirety at runtime. The kind of library that becomes part of an application's executable is known as a static library. Static libraries are collections or archives of object files.

A better approach is for an application to load code into its address space when it’s actually needed, either at launch time or at runtime. The type of library that provides this flexibility is called a dynamic library. Dynamic libraries are not statically linked into the executable and therefore do not become part of the executable. Instead, dynamic libraries can be loaded (and linked) into an application either when the application is launched or as it runs.

Example dynamic library

test.pas:

library TestLibrary;
  
{$mode objfpc} {$H+}

uses
  // needed for UpperCase
  SysUtils;     

// library subroutine
function cvtString(strIn : string) : PChar; cdecl;
  begin
    cvtString := PChar(UpperCase(strIn));
  end;

// exported subroutine(s)
exports
  cvtString;
end.

Compile:

 fpc test.pas

which produces the dynamic library file named libtest.dylib.

Example application to load dynamic library

dynlibdemo.pas:

Program dynlibdemo;
  
{$mode objfpc}{$H+}

uses
   Dynlibs,
   SysUtils;

type
   // definition of the subroutine to be called as defined in the dynamic library to be loaded
   TcvtString = function(strToConvert : string) : PChar;  cdecl;

var
   // create suitable variable for the dynamic library subroutine
   cvtString : TcvtString;

   // create a handle for the dynamic library
   LibHandle : TLibHandle;

begin
   // load and get the dynamic library handle
   LibHandle := LoadLibrary(PChar('libtest.dylib'));

   // check whether loading was successful
   if LibHandle <> 0 then
     begin
       // assign address of the subroutine call to the variable cvtString
       Pointer(cvtString) := GetProcAddress(LibHandle, 'cvtString');

       // check whether a valid address has been returned
       if @cvtString <> nil then
         WriteLn(cvtString('hello world'))
       // error message on no valid address
       else
         WriteLn('GetLastOSError1 = ', SysErrorMessage(GetLastOSError));
     end
   else
     // error message on load failure
     WriteLn('GetLastOSError2 = ', SysErrorMessage(GetLastOSError));

   // release memory
   cvtString := nil;
   FreeLibrary(LibHandle);
end.

Compile:

 fpc dynlibdemo.pas

Run:

$ ./dynlibdemo
HELLO WORLD

Additional steps using a Lazarus project

If you turn test.pas above into a Lazarus project named project1 and create an application bundle then you need to:

  • Delete the project1.app/Contents/MacOS/project1 symbolic link to the executable file
  • Copy the project1 executable file into the project1.app/Contents/MacOS/directory
  • Copy libTestLibrary.dylib into the project1.app/Contents/MacOS directory
  • Change into the project1.app/Contents/MacOS directory
  • Enter: install_name_tool -add_rpath @executable_path/. project1

This ensures that the project1 executable in your application bundle will look in the directory in which it is located to find your dynamic library.

For more information on the install_name_tool utility, open a Terminal and enter:

 man install_name_tool

See also