Sign in Join
Wiki copytemporary

macOS Dynamic Libraries: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: macOS Dynamic Libraries

OldNewLine
1+## Example FPC dynamic library
2+
3+> **Tip:** If you use "cdecl", FPC ensures that your function completely adheres to all ABI requirements (naming, parameter passing, etc). If you don't, then you are on your own.
4+
5+test.pas:
6+
7+``` pascal
8+library TestLibrary;
9+
10+{$mode objfpc} {$H+}
11+
12+uses
13+ // needed for UpperCase
14+ SysUtils;
15+
16+// library subroutine
17+function cvtString(strIn : string) : PChar; cdecl;
18+ begin
19+ cvtString := PChar(UpperCase(strIn));
20+ end;
21+
22+// exported subroutine(s)
23+exports
24+ cvtString;
25+end.
26+```
27+
28+Compile:
29+
30+` fpc test.pas`
31+
32+which produces the dynamic library file named libtest.dylib.
33+
34+## Universal 32 bit and 64 bit dynamic library
35+
36+If needed to support both 32 bit and 64 bit architectures, `test.pas` can be compiled as a universal dynamic library that contains both 32 bit and 64 bit libraries:
37+
38+`ppc386 test.pas`\
39+`ppcx64 -olibtest64.dylib test.pas`\
40+`lipo -create libtest.dylib libtest64.dylib -output libtest.dylib`
41+
42+To check that both architectures have been included in the dynamic library:
43+
44+`$ file libtest.dylib`\
45+`libtest.dylib: Mach-O universal binary with 2 architectures: [i386:Mach-O dynamically linked shared library i386] [x86_64:Mach-O 64-bit dynamically linked shared library x86_64]`\
46+`libtest.dylib (for architecture i386): Mach-O dynamically linked shared library i386`\
47+`libtest.dylib (for architecture x86_64):   Mach-O 64-bit dynamically linked shared library x86_64`
48+
49+Too easy!
50+
51+## Example application to load FPC dynamic library
52+
53+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.
54+
55+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.
56+
57+dynlibdemo.pas:
58+
59+``` pascal
60+Program dynlibdemo;
61+
62+{$mode objfpc}{$H+}
63+
64+uses
65+ Dynlibs,
66+ SysUtils;
67+
68+type
69+ // definition of the subroutine to be called as defined in the dynamic library to be loaded
70+ TcvtString = function(strToConvert : string) : PChar; cdecl;
71+
72+var
73+ // create suitable variable for the dynamic library subroutine
74+ cvtString : TcvtString;
75+
76+ // create a handle for the dynamic library
77+ LibHandle : TLibHandle;
78+
79+begin
80+ // load and get the dynamic library handle
81+ LibHandle := LoadLibrary(PChar('libtest.dylib'));
82+
83+ // check whether loading was successful
84+ if LibHandle <> 0 then
85+ begin
86+ // assign address of the subroutine call to the variable cvtString
87+ Pointer(cvtString) := GetProcAddress(LibHandle, 'cvtString');
88+
89+ // check whether a valid address has been returned
90+ if @cvtString <> nil then
91+ WriteLn(cvtString('hello world'))
92+ // error message on no valid address
93+ else
94+ WriteLn('GetLastOSError1 = ', SysErrorMessage(GetLastOSError));
95+ end
96+ else
97+ // error message on load failure
98+ WriteLn('GetLastOSError2 = ', SysErrorMessage(GetLastOSError));
99+
100+ // release memory
101+ cvtString := nil;
102+
103+ // unload library
104+ if LibHandle <> NilHandle then
105+ UnloadLibrary(LibHandle);
106+
107+ LibHandle := NilHandle;
108+end.
109+```
110+
111+Compile:
112+
113+` fpc dynlibdemo.pas`
114+
115+Run:
116+
117+``` bash
118+$ ./dynlibdemo
119+HELLO WORLD
120+```
121+
122+## Alternative example application to load FPC dynamic library
123+
124+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.
125+
126+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.
127+
128+alt_dynlibdemo.pas:
129+
130+``` pascal
131+{$linklib libtest}
132+
133+program alt_dynlibdemo;
134+
135+{$mode objfpc} {$H+}
136+
137+function cvtString(const strToConvert: string): PChar; cdecl; external;
138+
139+begin
140+ WriteLn(cvtString('hello world'));
141+end.
142+```
143+
144+Compile:
145+
146+` fpc alt_dynlibdemo.pas`
147+
148+Run:
149+
150+``` bash
151+$ ./alt_dynlibdemo
152+HELLO WORLD
153+```
154+
155+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:
156+
157+``` bash
158+$ ./alt_dynlibdemo
159+dyld: Library not loaded: /Users/[user]/fpc_dynamic_lib2/libtest.dylib
160+ Referenced from: /Users/[user]/fpc_dynamic_lib2/./alt_dynlibdemo
161+ Reason: image not found
162+Abort
163+```
164+
165+## Additional steps using a Lazarus project
166+
167+If you turn the pure FPC dynlibdemo.pas example above into a Lazarus project named project1 your unit should resemble:
168+
169+``` pascal
170+unit Unit1;
171+
172+{$mode objfpc}{$H+}
173+
174+interface
175+
176+uses
177+ Dynlibs,
178+ Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;
179+
180+type
181+ // definition of the subroutine to be called as defined in the dynamic library to be loaded
182+ TcvtString = function(strToConvert : string) : PChar; cdecl;
183+
184+type
185+
186+ { TForm1 }
187+
188+ TForm1 = class(TForm)
189+ Button1: TButton;
190+ procedure Button1Click(Sender: TObject);
191+ private
192+
193+ public
194+
195+ end;
196+
197+var
198+ Form1: TForm1;
199+
200+implementation
201+
202+{$R *.lfm}
203+
204+{ TForm1 }
205+
206+procedure TForm1.Button1Click(Sender: TObject);
207+var
208+ // create suitable variable for the dynamic library subroutine
209+ cvtString : TcvtString;
210+ // create a handle for the dynamic library
211+ LibHandle : TLibHandle;
212+begin
213+ // load and get the dynamic library handle
214+ LibHandle := LoadLibrary(PChar('libtestlibrary.dylib'));
215+
216+ // check whether loading was successful
217+ if LibHandle <> 0 then
218+ begin
219+ // assign address of the subroutine call to the variable cvtString
220+ Pointer(cvtString) := GetProcAddress(LibHandle, 'cvtString');
221+
222+ // check whether a valid address has been returned
223+ if @cvtString <> nil then
224+ ShowMessage(cvtString('hello world'))
225+ // error message on no valid address
226+ else
227+ ShowMessage('GetLastOSError1 = ' + SysErrorMessage(GetLastOSError));
228+ end
229+ else
230+ // error message on load failure
231+ ShowMessage('GetLastOSError2 = ' + SysErrorMessage(GetLastOSError));
232+
233+ // release memory
234+ cvtString := nil;
235+
236+ // unload library
237+ if LibHandle <> NilHandle then
238+ UnloadLibrary(LibHandle);
239+
240+ LibHandle := NilHandle;
241+end;
242+
243+end.
244+```
245+
246+After creating an [[Application Bundle|application bundle]] then you need to:
247+
248+- Open a Terminal (Applications \> Utilities \> Terminal)
249+- Delete the `project1.app/Contents/MacOS/project1` symbolic link to the project1 executable file
250+- Copy the project1 executable file into the `project1.app/Contents/MacOS` directory
251+- Copy libtest.dylib into the `project1.app/Contents/Frameworks` directory (you need to create the Frameworks directory)
252+- Change into the `project1.app/Contents/MacOS` directory
253+- Either:
254+ - Enter: `install_name_tool -add_rpath "@executable_path/../Frameworks/." project1` **=or=**
255+ - In the Lazarus IDE go to Project \> Project Options \> Compiler Options \> Compilation and Linking and there you will have to check the option "Pass options to linker with -k, delimiter is space" and enter the following: `-rpath @executable_path/../Frameworks` (Do NOT include -k which Lazarus adds for FPC automatically).
256+
257+This ensures that the project1 executable in your application bundle will look in your application bundle's Frameworks directory to find your dynamic library.
258+
259+For more information on the install_name_tool command line utility, open a Terminal and enter:
260+
261+` man install_name_tool`
262+
263+For more information on the linker command line utility, open a Terminal and enter:
264+
265+` man ld`
266+
267+## Example application to load libc dynamic library
268+
269+demo.pas
270+
271+``` pascal
272+{$mode objfpc}
273+{$linklib C}
274+
275+// Declarations for the standard C functions strlen and toupper
276+function strlen (P : pchar) : longint; cdecl; external;
277+function toupper(P : integer) : integer; cdecl; external;
278+
279+begin
280+ Write('"Programming is fun!" is ');
281+ Write(strlen('Programming is fun!'));
282+ WriteLn(' characters long.');
283+ WriteLn('Before: "c" and after "' + chr(toupper(ord('c'))) + '".');
284+end.
285+```
286+
287+Compile:
288+
289+` fpc demo.pas`
290+
291+Run:
292+
293+` $./demo`\
294+` "Programming is fun!" is 19 characters long.`\
295+` Before: "c" and after "C".`
296+
297+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:
298+
299+- libc
300+ - The standard C library. This library contains the functions used by C programmers on all platforms.
301+- libinfo
302+ - The NetInfo library.
303+- libkvm
304+ - The kernel virtual memory library.
305+- libm
306+ - The math library, which contains arithmetic functions.
307+- libpthread
308+ - The POSIX threads library, which allows multiple tasks to run concurrently within a single program.
309+- libutil
310+ - The library that provides functions related to login, logout, terminal assignment, and logging.
311+
312+There are symbolic links in `/usr/lib` for all of these libraries pointing to `/usr/lib/libSystem.dylib` **except** libutil.
313+
314+## Observe the dynamic linker in action
315+
316+If you want to observe the dynamic linker in action, you can use the command line utility `dtruss`. Open a Terminal and run:
317+
318+`$ sudo dtruss ./dynlibdemo`
319+
320+which will produce output similar to:
321+
322+```text
323+SYSCALL(args) = return
324+HELLO WORLD
325+...
326+stat64("libtest.dylib\0", 0x7FFEE96A3A20, 0x0) = 0 0
327+open("libtest.dylib\0", 0x0, 0x0) = 3 0
328+...
329+fcntl(0x3, 0x62, 0x7FFEE969B1B0) = 0 0
330+mmap(0x106726000, 0x84000, 0x5, 0x12, 0x3, 0x0) = 0x106726000 0
331+mmap(0x1067AA000, 0x1D000, 0x3, 0x12, 0x3, 0x84000) = 0x1067AA000 0
332+mmap(0x1067C9000, 0x2A28, 0x1, 0x12, 0x3, 0xA1000) = 0x1067C9000 0
333+fcntl(0x3, 0x32, 0x7FFEE969B440) = 0 0
334+close(0x3) = 0 0
335+...
336+```
337+
338+It is even more instructive to temporarily rename your dynamic library so it will not be found. This will produce output similar to:
339+
340+```text
341+...
342+stat64("libtest.dylib\0", 0x7FFEE00E95E0, 0x0) = -1 Err#2
343+stat64("libtest.dylib\0", 0x7FFEE00E9A20, 0x0) = -1 Err#2
344+stat64("/Users/[user]/lib/libtest.dylib\0", 0x7FFEE00E9990, 0x0) = -1 Err#2
345+stat64("/Users/[user]/lib/libtest.dylib\0", 0x7FFEE00E9DD0, 0x0) = -1 Err#2
346+stat64("/\0", 0x7FFEE00E7D78, 0x0) = 0 0
347+getattrlist("/Users\0", 0x117102048, 0x7FFEE00E96D0) = 0 0
348+getattrlist("/Users/[user]\0", 0x117102048, 0x7FFEE00E96D0) = 0 0
349+getattrlist("/Users/[user]/lib\0", 0x117102048, 0x7FFEE00E96D0) = -1 Err#2
350+stat64("/Users/[user]/lib\0", 0x7FFEE00E9560, 0x0) = -1 Err#2
351+stat64("/Users/[user]/lib\0", 0x7FFEE00E99A0, 0x0) = -1 Err#2
352+stat64("/usr/local/lib/libtest.dylib\0", 0x7FFEE00E9990, 0x0) = -1 Err#2
353+stat64("/usr/local/lib/libtest.dylib\0", 0x7FFEE00E9DD0, 0x0) = -1 Err#2
354+getattrlist("/usr\0", 0x117102048, 0x7FFEE00E96D0) = 0 0
355+getattrlist("/usr/local\0", 0x117102048, 0x7FFEE00E96D0) = 0 0
356+getattrlist("/usr/local/lib\0", 0x117102048, 0x7FFEE00E96D0) = 0 0
357+getattrlist("/usr/local/lib/libtest.dylib\0", 0x117102048, 0x7FFEE00E96D0) = -1 Err#2
358+stat64("/usr/local/lib/libtest.dylib\0", 0x7FFEE00E9560, 0x0) = -1 Err#2
359+stat64("/usr/local/lib/libtest.dylib\0", 0x7FFEE00E99A0, 0x0) = -1 Err#2
360+stat64("/usr/lib/libtest.dylib\0", 0x7FFEE00E99A0, 0x0) = -1 Err#2
361+stat64("/usr/lib/libtest.dylib\0", 0x7FFEE00E9DE0, 0x0) = -1 Err#2
362+getattrlist("/usr\0", 0x117102048, 0x7FFEE00E96E0) = 0 0
363+getattrlist("/usr/lib\0", 0x117102048, 0x7FFEE00E96E0) = 0 0
364+getattrlist("/usr/lib/libtest.dylib\0", 0x117102048, 0x7FFEE00E96E0) = -1 Err#2
365+stat64("/usr/lib/libtest.dylib\0", 0x7FFEE00E9570, 0x0) = -1 Err#2
366+stat64("/usr/lib/libtest.dylib\0", 0x7FFEE00E99B0, 0x0) = -1 Err#2
367+open(".\0", 0x0, 0x1) = 3 0
368+fstat64(0x3, 0x7FFEE00E7CF0, 0x0) = 0 0
369+fcntl(0x3, 0x32, 0x7FFEE00E9CB0) = 0 0
370+close(0x3) = 0 0
371+stat64("/Users/[user]/LAZPROJECTS/fpc_dynamic_lib\0", 0x7FFEE00E7C60, 0x0) = 0 0
372+stat64("/Users/[user]/LAZPROJECTS/fpc_dynamic_lib\0", 0x7FFEE00E7EF8, 0x0) = 0 0
373+getattrlist("/Users/[user]/LAZPROJECTS/fpc_dynamic_lib/libtest.dylib\0", 0x117102048, 0x7FFEE00E9850) = -1 Err#2
374+stat64("/Users/[user]/LAZPROJECTS/fpc_dynamic_lib/libtest.dylib\0", 0x7FFEE00E96E0, 0x0) = -1 Err#2
375+stat64("/Users/[user]/LAZPROJECTS/fpc_dynamic_lib/libtest.dylib\0", 0x7FFEE00E9B20, 0x0) = -1 Err#2
376+...
377+```
378+
379+The highlighted lines above show all the places the the system looked for the missing dynamic library.
380+
381+## Standard locations for dynamic libraries
382+
383+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 was locked-down in macOS 10.15 (Catalina) and later on the system read-only volume. You need to use the standard locations if you want to share the dynamic library among multiple applications.
384+
385+If you must place the dynamic library file in a non-standard location in the file system, you then must also add that location to one of these environment variables:
386+
387+- LD_LIBRARY_PATH
388+- DYLD_LIBRARY_PATH
389+- DYLD_FALLBACK_LIBRARY_PATH
390+
391+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` so it is best to store your dynamic library or libraries in the application bundle.
392+
393+For applications with a graphical user interface it not only makes sense to include any needed libraries in your application's Frameworks folder, Apple encourages it.
394+
395+## Logging dynamic loader events
396+
397+As you develop and use dynamic libraries, you may want to know when certain events occur. For example, you want to know when the dynamic loader binds a particular undefined external symbol or how long it took for an application to launch. Environment variables to the rescue. The environment variables that affect dynamic loader logging are:
398+
399+| Environment variable | Description |
400+|----|----|
401+| DYLD_PRINT_LIBRARIES | Logs when images are loaded. |
402+| DYLD_PRINT_LIBRARIES_POST_LAUNCH | Logs when images are loaded as a result of a dlopen call. Includes a dynamic libraries’ dependent libraries. |
403+| DYLD_PRINT_APIS | Logs the invocation that causes the dynamic loader to return the address of a symbol. |
404+| DYLD_PRINT_STATISTICS | Logs statistical information on an application’s launch process, such as how many images were loaded, when the application finishes launching. |
405+| DYLD_PRINT_INITIALIZERS | Logs when the dynamic loader calls initializer and finalizer functions. |
406+| DYLD_PRINT_SEGMENTS | Logs when the dynamic loader maps a segment of a dynamic library to the current process’s address space. |
407+| DYLD_PRINT_BINDINGS | Logs when the dynamic loader binds an undefined external symbol with its definition. |
408+
409+For example, Open an Applications \> Utilities \> Terminal - note the shell being used below is `tcsh`:
410+
411+`$ setenv DYLD_PRINT_STATISTICS`\
412+`$ ./my_app`\
413+`Total pre-main time:  13.41 milliseconds (100.0%)`\
414+`        dylib loading time:   6.39 milliseconds (47.6%)`\
415+`       rebase/binding time:   0.97 milliseconds (7.2%)`\
416+`           ObjC setup time:   0.46 milliseconds (3.4%)`\
417+`          initializer time:   5.40 milliseconds (40.2%)`\
418+`          slowest intializers :`\
419+`            libSystem.B.dylib :   1.19 milliseconds (8.9%)`\
420+`              libobjc.A.dylib :   0.47 milliseconds (3.5%)`\
421+`               CoreFoundation :   2.35 milliseconds (17.5%)`\
422+`                       AppKit :   0.61 milliseconds (4.6%)`\
423+`$ unsetenv DYLD_PRINT_STATISTICS`
424+
425+For the default macOS shell `zsh`, use `export DYLD_PRINT_STATISTICS=1` and `unset DYLD_PRINT_STATISTICS` to set and unset an environment variable.
426+
427+## See also
428+
429+- [[Application Bundle]] - structure of a macOS application
430+
431+- [[macOS Static Libraries]] for the same FPC library being used as a static library.
432+
433+- [[macOS Frameworks]] - structured directories for dynamic libraries and associated resources.
434+
435+- [[macOS Programming Tips|macOS Libraries]].
436+
437+- [[shared library|FPC Shared Libraries]].
438+
439+- [[macOS Big Sur changes for developers|Big Sur's built-in dynamic linker cache]].
440+
441+## External links
442+
443+- [Apple Documentation: Dynamic Library Programming Topics](https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/DynamicLibraries/000-Introduction/Introduction.html#//apple_ref/doc/uid/TP40001908-SW1).
444+
445+---
446+*From the Free Pascal wiki, page "macOS Dynamic Libraries", as it stood on 2021-09-08 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history