Difference between revisions of "macOS Dynamic Libraries"
m (→See also: Added entry for Static Library article) |
(Added page template; moved categories to page template; added Lazarus unit example) |
||
Line 1: | Line 1: | ||
+ | {{macOS Dynamic Libraries}} | ||
+ | |||
{{macOS library overview}} | {{macOS library overview}} | ||
− | = Example FPC dynamic library = | + | == Example FPC dynamic library == |
test.pas: | test.pas: | ||
Line 32: | Line 34: | ||
which produces the dynamic library file named libtest.dylib. | which produces the dynamic library file named libtest.dylib. | ||
− | = Example application to load FPC dynamic library = | + | == Example application to load FPC dynamic library == |
This version of the example application loads the dynamic library on demand when necessary (load-time dynamic linking aka dynamic loading) and can also unload it when it is no longer necessary. You can successfully compile the application even if the dynamic library does not exist. | This version of the example application loads the dynamic library on demand when necessary (load-time dynamic linking aka dynamic loading) and can also unload it when it is no longer necessary. You can successfully compile the application even if the dynamic library does not exist. | ||
Line 98: | Line 100: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | = Alternative example application to load FPC dynamic library = | + | == Alternative example application to load FPC dynamic library == |
This version of the example application loads the dynamic library at startup (run-time dynamic linking aka dynamic linking) and cannot unload it. You cannot successfully compile the application unless the dynamic library exists. | This version of the example application loads the dynamic library at startup (run-time dynamic linking aka dynamic linking) and cannot unload it. You cannot successfully compile the application unless the dynamic library exists. | ||
Line 140: | Line 142: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | = Additional steps using a Lazarus project = | + | == Additional steps using a Lazarus project == |
+ | |||
+ | If you turn the pure FPC test.pas above into a Lazarus project named project1 your unit should resemble: | ||
+ | |||
+ | <syntaxhighlight lang=pascal> | ||
+ | unit Unit1; | ||
+ | |||
+ | {$mode objfpc}{$H+} | ||
+ | |||
+ | interface | ||
+ | |||
+ | uses | ||
+ | Dynlibs, | ||
+ | Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls; | ||
+ | |||
+ | type | ||
+ | // definition of the subroutine to be called as defined in the dynamic library to be loaded | ||
+ | TcvtString = function(strToConvert : string) : PChar; cdecl; | ||
+ | |||
+ | type | ||
+ | |||
+ | { TForm1 } | ||
− | + | TForm1 = class(TForm) | |
+ | Button1: TButton; | ||
+ | procedure Button1Click(Sender: TObject); | ||
+ | private | ||
+ | |||
+ | public | ||
+ | |||
+ | end; | ||
+ | |||
+ | var | ||
+ | Form1: TForm1; | ||
+ | |||
+ | implementation | ||
+ | |||
+ | {$R *.lfm} | ||
+ | |||
+ | { TForm1 } | ||
+ | |||
+ | procedure TForm1.Button1Click(Sender: TObject); | ||
+ | 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('libtestlibrary.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 | ||
+ | ShowMessage(cvtString('hello world')) | ||
+ | // error message on no valid address | ||
+ | else | ||
+ | ShowMessage('GetLastOSError1 = ' + SysErrorMessage(GetLastOSError)); | ||
+ | end | ||
+ | else | ||
+ | // error message on load failure | ||
+ | ShowMessage('GetLastOSError2 = ' + SysErrorMessage(GetLastOSError)); | ||
+ | |||
+ | // release memory | ||
+ | cvtString := nil; | ||
+ | FreeLibrary(LibHandle); | ||
+ | end; | ||
+ | |||
+ | end. | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | After creating an [[Application_Bundle|application bundle]] then you need to: | ||
* Open a Terminal (Applications > Utilities > Terminal) | * Open a Terminal (Applications > Utilities > Terminal) | ||
Line 156: | Line 232: | ||
man install_name_tool | man install_name_tool | ||
− | = Example application to load libc dynamic library = | + | == Example application to load libc dynamic library == |
demo.pas | demo.pas | ||
Line 203: | Line 279: | ||
There are symbolic links in <tt>/usr/lib</tt> for all of these libraries pointing to <tt>/usr/lib/libSystem.dylib</tt> '''except''' libutil. | There are symbolic links in <tt>/usr/lib</tt> for all of these libraries pointing to <tt>/usr/lib/libSystem.dylib</tt> '''except''' libutil. | ||
− | = Observe the dynamic linker in action = | + | == Observe the dynamic linker in action == |
If you want to observe the dynamic linker in action, you can use the command line utility <syntaxhighlight lang=text inline>dtruss</syntaxhighlight>. Open a Terminal and run: | If you want to observe the dynamic linker in action, you can use the command line utility <syntaxhighlight lang=text inline>dtruss</syntaxhighlight>. Open a Terminal and run: | ||
Line 270: | Line 346: | ||
The highlighted lines above show all the places the the system looked for the missing dynamic library. | The highlighted lines above show all the places the the system looked for the missing dynamic library. | ||
− | = Standard locations for dynamic libraries = | + | == Standard locations for dynamic libraries == |
While in the above example we stored the dynamic library in the application bundle, the standard locations for dynamic libraries are <tt>~/lib</tt> (for single user) and <tt>/usr/local/lib</tt> (for multiple users). There used to also be <tt>/usr/lib</tt>, but that is now locked-down in macOS 10.15 (Catalina) on the system read-only volume. You need to use the standard locations if you want to share the dynamic library among multiple applications. | While in the above example we stored the dynamic library in the application bundle, the standard locations for dynamic libraries are <tt>~/lib</tt> (for single user) and <tt>/usr/local/lib</tt> (for multiple users). There used to also be <tt>/usr/lib</tt>, but that is now locked-down in macOS 10.15 (Catalina) on the system read-only volume. You need to use the standard locations if you want to share the dynamic library among multiple applications. | ||
Line 276: | Line 352: | ||
Note: You cannot distribute an application through the Mac App Store that depends on a custom library being installed first by the user into <tt>/usr/local/lib</tt>. | Note: You cannot distribute an application through the Mac App Store that depends on a custom library being installed first by the user into <tt>/usr/local/lib</tt>. | ||
− | = See also = | + | == See also == |
* [[macOS Static Libraries]] for the same FPC library being used as a static library. | * [[macOS Static Libraries]] for the same FPC library being used as a static library. | ||
− | * [[macOS_Programming_Tips#Libraries|macOS Libraries]] | + | * [[macOS_Programming_Tips#Libraries|macOS Libraries]]. |
− | |||
− | |||
− | [ | + | * [https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/DynamicLibraries/000-Introduction/Introduction.html#//apple_ref/doc/uid/TP40001908-SW1 Apple Documentation: Dynamic Library Programming Topics]. |
− | |||
− | |||
− | |||
− |
Revision as of 02:53, 26 May 2020
│ English (en) │
This article applies to macOS only.
See also: Multiplatform Programming Guide
Overview
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 launch time. 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.
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. Another reason to use dynamic libraries is so that you can share code among multiple applications thereby saving the memory (and to a lesser extent nowadays, disk space) that would otherwise be used for multiple copies of the library code.
There are, however, some advantages to statically linking libraries with an executable instead of dynamically linking them. The most significant advantage is that the application can be certain that all its libraries are present and that they are the correct version. Static linking of libraries also allows the application to be contained in a single executable file, simplifying distribution and installation. Also with static linking, only those parts of the library that are directly and indirectly referenced by the target executable are included in the executable. With dynamic libraries, the entire library is loaded, as it is not known in advance which functions will be used by the application. Whether this advantage is significant in practice depends on the structure of the library.
Library extensions and prefixes
Operating System | Dynamic library | Static library | Library prefix |
---|---|---|---|
FreeBSD | .so | .a | lib |
macOS | .dylib | .a | lib |
Linux | .so | .a | lib |
Windows | .dll | .lib | n/a |
The library prefix column indicates how the names of the libraries are resolved and created. Under macOS, the library name will always have the lib prefix when it is created. So if you create a dynamic library called test, this will result in the file libtest.dylib. When importing routines from shared libraries, it is not necessary to give the library prefix or the filename extension.
Example FPC 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 FPC dynamic library
This version of the example application loads the dynamic library on demand when necessary (load-time dynamic linking aka dynamic loading) and can also unload it when it is no longer necessary. You can successfully compile the application even if the dynamic library does not exist.
The alternative example version that follows loads the dynamic library at start-up (run-time dynamic linking aka dynamic linking) and cannot unload it until the application quits. You cannot successfully compile the application unless the dynamic library exists.
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
Alternative example application to load FPC dynamic library
This version of the example application loads the dynamic library at startup (run-time dynamic linking aka dynamic linking) and cannot unload it. You cannot successfully compile the application unless the dynamic library exists.
The previous version of the example application loads the dynamic library when necessary (load-time dynamic linking aka dynamic loading) and can also unload it when it is no longer necessary. You can successfully compile the application even if the dynamic library does not exist.
dynlibdemo.pas:
{$linklib libtest}
program dynlibdemo;
{$mode objfpc} {$H+}
function cvtString(const strToConvert: string): PChar; cdecl; external;
begin
WriteLn(cvtString('hello world'));
end.
Compile:
fpc dynlibdemo.pas
Run:
$ ./dynlibdemo
HELLO WORLD
If you think the code looks like it statically links the library, you can verify that it does not by moving the library or renaming it and then re-running the demo application which produces output similar to this:
$ ./dynlibdemo
dyld: Library not loaded: /Users/[user]/fpc_dynamic_lib2/libtest.dylib
Referenced from: /Users/[user]/fpc_dynamic_lib2/./dynlibdemo
Reason: image not found
Abort
Additional steps using a Lazarus project
If you turn the pure FPC test.pas above into a Lazarus project named project1 your unit should resemble:
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Dynlibs,
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;
type
// definition of the subroutine to be called as defined in the dynamic library to be loaded
TcvtString = function(strToConvert : string) : PChar; cdecl;
type
{ TForm1 }
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
public
end;
var
Form1: TForm1;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
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('libtestlibrary.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
ShowMessage(cvtString('hello world'))
// error message on no valid address
else
ShowMessage('GetLastOSError1 = ' + SysErrorMessage(GetLastOSError));
end
else
// error message on load failure
ShowMessage('GetLastOSError2 = ' + SysErrorMessage(GetLastOSError));
// release memory
cvtString := nil;
FreeLibrary(LibHandle);
end;
end.
After creating an application bundle then you need to:
- Open a Terminal (Applications > Utilities > Terminal)
- Delete the project1.app/Contents/MacOS/project1 symbolic link to the project1 executable file
- Copy the project1 executable file into the project1.app/Contents/MacOS directory
- Copy libtest.dylib into the project1.app/Contents/Frameworks directory (you need to create the Frameworks directory)
- Change into the project1.app/Contents/MacOS directory
- Enter:
install_name_tool -add_rpath "@executable_path/../Frameworks/." project1
This ensures that the project1 executable in your application bundle will look in the Frameworks directory to find your dynamic library.
For more information on the install_name_tool command line utility, open a Terminal and enter:
man install_name_tool
Example application to load libc dynamic library
demo.pas
{$mode objfpc}
{$linklib C}
// Declarations for the standard C functions strlen and toupper
function strlen (P : pchar) : longint; cdecl; external;
function toupper(P : integer) : integer; cdecl; external;
begin
Write('"Programming is fun!" is ');
Write(strlen('Programming is fun!'));
WriteLn(' characters long.');
WriteLn('Before: "c" and after "' + chr(toupper(ord('c'))) + '".');
end.
Compile:
fpc demo.pas
Run:
$./demo "Programming is fun!" is 19 characters long. Before: "c" and after "C".
Be aware that unlike, for example FreeBSD and Linux, there is no static libc library on macOS. Instead, there is the dynamic system library (/usr/lib/libSystem.dylib) which includes the following libraries:
- libc
- The standard C library. This library contains the functions used by C programmers on all platforms.
- libinfo
- The NetInfo library.
- libkvm
- The kernel virtual memory library.
- libm
- The math library, which contains arithmetic functions.
- libpthread
- The POSIX threads library, which allows multiple tasks to run concurrently within a single program.
- libutil
- The library that provides functions related to login, logout, terminal assignment, and logging.
There are symbolic links in /usr/lib for all of these libraries pointing to /usr/lib/libSystem.dylib except libutil.
Observe the dynamic linker in action
If you want to observe the dynamic linker in action, you can use the command line utility dtruss
. Open a Terminal and run:
$ sudo dtruss ./dynlibdemo:
which will produce output similar to:
SYSCALL(args) = return
HELLO WORLD
...
stat64("libtest.dylib\0", 0x7FFEE96A3A20, 0x0) = 0 0
open("libtest.dylib\0", 0x0, 0x0) = 3 0
...
fcntl(0x3, 0x62, 0x7FFEE969B1B0) = 0 0
mmap(0x106726000, 0x84000, 0x5, 0x12, 0x3, 0x0) = 0x106726000 0
mmap(0x1067AA000, 0x1D000, 0x3, 0x12, 0x3, 0x84000) = 0x1067AA000 0
mmap(0x1067C9000, 0x2A28, 0x1, 0x12, 0x3, 0xA1000) = 0x1067C9000 0
fcntl(0x3, 0x32, 0x7FFEE969B440) = 0 0
close(0x3) = 0 0
...
It is even more instructive to temporarily rename your dynamic library so it will not be found. This will produce output similar to:
...
stat64("libtest.dylib\0", 0x7FFEE00E95E0, 0x0) = -1 Err#2
stat64("libtest.dylib\0", 0x7FFEE00E9A20, 0x0) = -1 Err#2
stat64("/Users/[user]/lib/libtest.dylib\0", 0x7FFEE00E9990, 0x0) = -1 Err#2
stat64("/Users/[user]/lib/libtest.dylib\0", 0x7FFEE00E9DD0, 0x0) = -1 Err#2
stat64("/\0", 0x7FFEE00E7D78, 0x0) = 0 0
getattrlist("/Users\0", 0x117102048, 0x7FFEE00E96D0) = 0 0
getattrlist("/Users/[user]\0", 0x117102048, 0x7FFEE00E96D0) = 0 0
getattrlist("/Users/[user]/lib\0", 0x117102048, 0x7FFEE00E96D0) = -1 Err#2
stat64("/Users/[user]/lib\0", 0x7FFEE00E9560, 0x0) = -1 Err#2
stat64("/Users/[user]/lib\0", 0x7FFEE00E99A0, 0x0) = -1 Err#2
stat64("/usr/local/lib/libtest.dylib\0", 0x7FFEE00E9990, 0x0) = -1 Err#2
stat64("/usr/local/lib/libtest.dylib\0", 0x7FFEE00E9DD0, 0x0) = -1 Err#2
getattrlist("/usr\0", 0x117102048, 0x7FFEE00E96D0) = 0 0
getattrlist("/usr/local\0", 0x117102048, 0x7FFEE00E96D0) = 0 0
getattrlist("/usr/local/lib\0", 0x117102048, 0x7FFEE00E96D0) = 0 0
getattrlist("/usr/local/lib/libtest.dylib\0", 0x117102048, 0x7FFEE00E96D0) = -1 Err#2
stat64("/usr/local/lib/libtest.dylib\0", 0x7FFEE00E9560, 0x0) = -1 Err#2
stat64("/usr/local/lib/libtest.dylib\0", 0x7FFEE00E99A0, 0x0) = -1 Err#2
stat64("/usr/lib/libtest.dylib\0", 0x7FFEE00E99A0, 0x0) = -1 Err#2
stat64("/usr/lib/libtest.dylib\0", 0x7FFEE00E9DE0, 0x0) = -1 Err#2
getattrlist("/usr\0", 0x117102048, 0x7FFEE00E96E0) = 0 0
getattrlist("/usr/lib\0", 0x117102048, 0x7FFEE00E96E0) = 0 0
getattrlist("/usr/lib/libtest.dylib\0", 0x117102048, 0x7FFEE00E96E0) = -1 Err#2
stat64("/usr/lib/libtest.dylib\0", 0x7FFEE00E9570, 0x0) = -1 Err#2
stat64("/usr/lib/libtest.dylib\0", 0x7FFEE00E99B0, 0x0) = -1 Err#2
open(".\0", 0x0, 0x1) = 3 0
fstat64(0x3, 0x7FFEE00E7CF0, 0x0) = 0 0
fcntl(0x3, 0x32, 0x7FFEE00E9CB0) = 0 0
close(0x3) = 0 0
stat64("/Users/[user]/LAZPROJECTS/fpc_dynamic_lib\0", 0x7FFEE00E7C60, 0x0) = 0 0
stat64("/Users/[user]/LAZPROJECTS/fpc_dynamic_lib\0", 0x7FFEE00E7EF8, 0x0) = 0 0
getattrlist("/Users/[user]/LAZPROJECTS/fpc_dynamic_lib/libtest.dylib\0", 0x117102048, 0x7FFEE00E9850) = -1 Err#2
stat64("/Users/[user]/LAZPROJECTS/fpc_dynamic_lib/libtest.dylib\0", 0x7FFEE00E96E0, 0x0) = -1 Err#2
stat64("/Users/[user]/LAZPROJECTS/fpc_dynamic_lib/libtest.dylib\0", 0x7FFEE00E9B20, 0x0) = -1 Err#2
...
The highlighted lines above show all the places the the system looked for the missing dynamic library.
Standard locations for dynamic libraries
While in the above example we stored the dynamic library in the application bundle, the standard locations for dynamic libraries are ~/lib (for single user) and /usr/local/lib (for multiple users). There used to also be /usr/lib, but that is now locked-down in macOS 10.15 (Catalina) on the system read-only volume. You need to use the standard locations if you want to share the dynamic library among multiple applications.
Note: You cannot distribute an application through the Mac App Store that depends on a custom library being installed first by the user into /usr/local/lib.
See also
- macOS Static Libraries for the same FPC library being used as a static library.