Sign in Join
Wiki copytemporary

macOS Dynamic Libraries: what changed

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

OldNewLine
11## Example FPC dynamic library
22
33> **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.
44
55test.pas:
66
77``` pascal
88library TestLibrary;
99
1010{$mode objfpc} {$H+}
1111
1212uses
1313 // needed for UpperCase
1414 SysUtils;
1515
1616// library subroutine
1717function cvtString(strIn : string) : PChar; cdecl;
1818 begin
1919 cvtString := PChar(UpperCase(strIn));
2020 end;
2121
2222// exported subroutine(s)
2323exports
2424 cvtString;
2525end.
2626```
2727
2828Compile:
2929
30-` fpc test.pas`
30+```text
31+ fpc test.pas
32+```
3133
3234which produces the dynamic library file named libtest.dylib.
3335
3436## Universal 32 bit and 64 bit dynamic library
3537
3638If 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:
3739
38-`ppc386 test.pas`\
39-`ppcx64 -olibtest64.dylib test.pas`\
40-`lipo -create libtest.dylib libtest64.dylib -output libtest.dylib`
40+```text
41+ppc386 test.pas
42+ppcx64 -olibtest64.dylib test.pas
43+lipo -create libtest.dylib libtest64.dylib -output libtest.dylib
44+```
4145
4246To check that both architectures have been included in the dynamic library:
4347
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+```text
49+$ file libtest.dylib
50+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]
51+libtest.dylib (for architecture i386): Mach-O dynamically linked shared library i386
52+libtest.dylib (for architecture x86_64): Mach-O 64-bit dynamically linked shared library x86_64
53+```
4854
4955Too easy!
5056
5157## Example application to load FPC dynamic library
5258
5359This 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.
5460
5561The 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.
5662
5763dynlibdemo.pas:
5864
5965``` pascal
6066Program dynlibdemo;
6167
6268{$mode objfpc}{$H+}
6369
6470uses
6571 Dynlibs,
6672 SysUtils;
6773
6874type
6975 // definition of the subroutine to be called as defined in the dynamic library to be loaded
7076 TcvtString = function(strToConvert : string) : PChar; cdecl;
7177
7278var
7379 // create suitable variable for the dynamic library subroutine
7480 cvtString : TcvtString;
7581
7682 // create a handle for the dynamic library
7783 LibHandle : TLibHandle;
7884
7985begin
8086 // load and get the dynamic library handle
8187 LibHandle := LoadLibrary(PChar('libtest.dylib'));
8288
8389 // check whether loading was successful
8490 if LibHandle <> 0 then
8591 begin
8692 // assign address of the subroutine call to the variable cvtString
8793 Pointer(cvtString) := GetProcAddress(LibHandle, 'cvtString');
8894
8995 // check whether a valid address has been returned
9096 if @cvtString <> nil then
9197 WriteLn(cvtString('hello world'))
9298 // error message on no valid address
9399 else
94100 WriteLn('GetLastOSError1 = ', SysErrorMessage(GetLastOSError));
95101 end
96102 else
97103 // error message on load failure
98104 WriteLn('GetLastOSError2 = ', SysErrorMessage(GetLastOSError));
99105
100106 // release memory
101107 cvtString := nil;
102108
103109 // unload library
104110 if LibHandle <> NilHandle then
105111 UnloadLibrary(LibHandle);
106112
107113 LibHandle := NilHandle;
108114end.
109115```
110116
111117Compile:
112118
113-` fpc dynlibdemo.pas`
119+```text
120+ fpc dynlibdemo.pas
121+```
114122
115123Run:
116124
117125``` bash
118126$ ./dynlibdemo
119127HELLO WORLD
120128```
121129
122130## Alternative example application to load FPC dynamic library
123131
124132This 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.
125133
126134The 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.
127135
128136alt_dynlibdemo.pas:
129137
130138``` pascal
131139{$linklib libtest}
132140
133141program alt_dynlibdemo;
134142
135143{$mode objfpc} {$H+}
136144
137145function cvtString(const strToConvert: string): PChar; cdecl; external;
138146
139147begin
140148 WriteLn(cvtString('hello world'));
141149end.
142150```
143151
144152Compile:
145153
146-` fpc alt_dynlibdemo.pas`
154+```text
155+ fpc alt_dynlibdemo.pas
156+```
147157
148158Run:
149159
150160``` bash
151161$ ./alt_dynlibdemo
152162HELLO WORLD
153163```
154164
155165If 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:
156166
157167``` bash
158168$ ./alt_dynlibdemo
159169dyld: Library not loaded: /Users/[user]/fpc_dynamic_lib2/libtest.dylib
160170 Referenced from: /Users/[user]/fpc_dynamic_lib2/./alt_dynlibdemo
161171 Reason: image not found
162172Abort
163173```
164174
165175## Additional steps using a Lazarus project
166176
167177If you turn the pure FPC dynlibdemo.pas example above into a Lazarus project named project1 your unit should resemble:
168178
169179``` pascal
170180unit Unit1;
171181
172182{$mode objfpc}{$H+}
173183
174184interface
175185
176186uses
177187 Dynlibs,
178188 Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;
179189
180190type
181191 // definition of the subroutine to be called as defined in the dynamic library to be loaded
182192 TcvtString = function(strToConvert : string) : PChar; cdecl;
183193
184194type
185195
186196 { TForm1 }
187197
188198 TForm1 = class(TForm)
189199 Button1: TButton;
190200 procedure Button1Click(Sender: TObject);
191201 private
192202
193203 public
194204
195205 end;
196206
197207var
198208 Form1: TForm1;
199209
200210implementation
201211
202212{$R *.lfm}
203213
204214{ TForm1 }
205215
206216procedure TForm1.Button1Click(Sender: TObject);
207217var
208218 // create suitable variable for the dynamic library subroutine
209219 cvtString : TcvtString;
210220 // create a handle for the dynamic library
211221 LibHandle : TLibHandle;
212222begin
213223 // load and get the dynamic library handle
214224 LibHandle := LoadLibrary(PChar('libtestlibrary.dylib'));
215225
216226 // check whether loading was successful
217227 if LibHandle <> 0 then
218228 begin
219229 // assign address of the subroutine call to the variable cvtString
220230 Pointer(cvtString) := GetProcAddress(LibHandle, 'cvtString');
221231
222232 // check whether a valid address has been returned
223233 if @cvtString <> nil then
224234 ShowMessage(cvtString('hello world'))
225235 // error message on no valid address
226236 else
227237 ShowMessage('GetLastOSError1 = ' + SysErrorMessage(GetLastOSError));
228238 end
229239 else
230240 // error message on load failure
231241 ShowMessage('GetLastOSError2 = ' + SysErrorMessage(GetLastOSError));
232242
233243 // release memory
234244 cvtString := nil;
235245
236246 // unload library
237247 if LibHandle <> NilHandle then
238248 UnloadLibrary(LibHandle);
239249
240250 LibHandle := NilHandle;
241251end;
242252
243253end.
244254```
245255
246256After creating an [[Application Bundle|application bundle]] then you need to:
247257
248258- Open a Terminal (Applications \> Utilities \> Terminal)
249259- Delete the `project1.app/Contents/MacOS/project1` symbolic link to the project1 executable file
250260- Copy the project1 executable file into the `project1.app/Contents/MacOS` directory
251261- Copy libtest.dylib into the `project1.app/Contents/Frameworks` directory (you need to create the Frameworks directory)
252262- Change into the `project1.app/Contents/MacOS` directory
253263- Either:
254264 - Enter: `install_name_tool -add_rpath "@executable_path/../Frameworks/." project1` **=or=**
255265 - 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).
256266
257267This ensures that the project1 executable in your application bundle will look in your application bundle's Frameworks directory to find your dynamic library.
258268
259269For more information on the install_name_tool command line utility, open a Terminal and enter:
260270
261-` man install_name_tool`
271+```text
272+ man install_name_tool
273+```
262274
263275For more information on the linker command line utility, open a Terminal and enter:
264276
265-` man ld`
277+```text
278+ man ld
279+```
266280
267281## Example application to load libc dynamic library
268282
269283demo.pas
270284
271285``` pascal
272286{$mode objfpc}
273287{$linklib C}
274288
275289// Declarations for the standard C functions strlen and toupper
276290function strlen (P : pchar) : longint; cdecl; external;
277291function toupper(P : integer) : integer; cdecl; external;
278292
279293begin
280294 Write('"Programming is fun!" is ');
281295 Write(strlen('Programming is fun!'));
282296 WriteLn(' characters long.');
283297 WriteLn('Before: "c" and after "' + chr(toupper(ord('c'))) + '".');
284298end.
285299```
286300
287301Compile:
288302
289-` fpc demo.pas`
303+```text
304+ fpc demo.pas
305+```
290306
291307Run:
292308
293-` $./demo`\
294-` "Programming is fun!" is 19 characters long.`\
295-` Before: "c" and after "C".`
309+```text
310+ $./demo
311+ "Programming is fun!" is 19 characters long.
312+ Before: "c" and after "C".
313+```
296314
297315Be 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:
298316
299317- libc
300318 - The standard C library. This library contains the functions used by C programmers on all platforms.
301319- libinfo
302320 - The NetInfo library.
303321- libkvm
304322 - The kernel virtual memory library.
305323- libm
306324 - The math library, which contains arithmetic functions.
307325- libpthread
308326 - The POSIX threads library, which allows multiple tasks to run concurrently within a single program.
309327- libutil
310328 - The library that provides functions related to login, logout, terminal assignment, and logging.
311329
312330There are symbolic links in `/usr/lib` for all of these libraries pointing to `/usr/lib/libSystem.dylib` **except** libutil.
313331
314332## Observe the dynamic linker in action
315333
316334If you want to observe the dynamic linker in action, you can use the command line utility `dtruss`. Open a Terminal and run:
317335
318-`$ sudo dtruss ./dynlibdemo`
336+```text
337+$ sudo dtruss ./dynlibdemo
338+```
319339
320340which will produce output similar to:
321341
322342```text
323343SYSCALL(args) = return
324344HELLO WORLD
325345...
326346stat64("libtest.dylib\0", 0x7FFEE96A3A20, 0x0) = 0 0
327347open("libtest.dylib\0", 0x0, 0x0) = 3 0
328348...
329349fcntl(0x3, 0x62, 0x7FFEE969B1B0) = 0 0
330350mmap(0x106726000, 0x84000, 0x5, 0x12, 0x3, 0x0) = 0x106726000 0
331351mmap(0x1067AA000, 0x1D000, 0x3, 0x12, 0x3, 0x84000) = 0x1067AA000 0
332352mmap(0x1067C9000, 0x2A28, 0x1, 0x12, 0x3, 0xA1000) = 0x1067C9000 0
333353fcntl(0x3, 0x32, 0x7FFEE969B440) = 0 0
334354close(0x3) = 0 0
335355...
336356```
337357
338358It is even more instructive to temporarily rename your dynamic library so it will not be found. This will produce output similar to:
339359
340360```text
341361...
342362stat64("libtest.dylib\0", 0x7FFEE00E95E0, 0x0) = -1 Err#2
343363stat64("libtest.dylib\0", 0x7FFEE00E9A20, 0x0) = -1 Err#2
344364stat64("/Users/[user]/lib/libtest.dylib\0", 0x7FFEE00E9990, 0x0) = -1 Err#2
345365stat64("/Users/[user]/lib/libtest.dylib\0", 0x7FFEE00E9DD0, 0x0) = -1 Err#2
346366stat64("/\0", 0x7FFEE00E7D78, 0x0) = 0 0
347367getattrlist("/Users\0", 0x117102048, 0x7FFEE00E96D0) = 0 0
348368getattrlist("/Users/[user]\0", 0x117102048, 0x7FFEE00E96D0) = 0 0
349369getattrlist("/Users/[user]/lib\0", 0x117102048, 0x7FFEE00E96D0) = -1 Err#2
350370stat64("/Users/[user]/lib\0", 0x7FFEE00E9560, 0x0) = -1 Err#2
351371stat64("/Users/[user]/lib\0", 0x7FFEE00E99A0, 0x0) = -1 Err#2
352372stat64("/usr/local/lib/libtest.dylib\0", 0x7FFEE00E9990, 0x0) = -1 Err#2
353373stat64("/usr/local/lib/libtest.dylib\0", 0x7FFEE00E9DD0, 0x0) = -1 Err#2
354374getattrlist("/usr\0", 0x117102048, 0x7FFEE00E96D0) = 0 0
355375getattrlist("/usr/local\0", 0x117102048, 0x7FFEE00E96D0) = 0 0
356376getattrlist("/usr/local/lib\0", 0x117102048, 0x7FFEE00E96D0) = 0 0
357377getattrlist("/usr/local/lib/libtest.dylib\0", 0x117102048, 0x7FFEE00E96D0) = -1 Err#2
358378stat64("/usr/local/lib/libtest.dylib\0", 0x7FFEE00E9560, 0x0) = -1 Err#2
359379stat64("/usr/local/lib/libtest.dylib\0", 0x7FFEE00E99A0, 0x0) = -1 Err#2
360380stat64("/usr/lib/libtest.dylib\0", 0x7FFEE00E99A0, 0x0) = -1 Err#2
361381stat64("/usr/lib/libtest.dylib\0", 0x7FFEE00E9DE0, 0x0) = -1 Err#2
362382getattrlist("/usr\0", 0x117102048, 0x7FFEE00E96E0) = 0 0
363383getattrlist("/usr/lib\0", 0x117102048, 0x7FFEE00E96E0) = 0 0
364384getattrlist("/usr/lib/libtest.dylib\0", 0x117102048, 0x7FFEE00E96E0) = -1 Err#2
365385stat64("/usr/lib/libtest.dylib\0", 0x7FFEE00E9570, 0x0) = -1 Err#2
366386stat64("/usr/lib/libtest.dylib\0", 0x7FFEE00E99B0, 0x0) = -1 Err#2
367387open(".\0", 0x0, 0x1) = 3 0
368388fstat64(0x3, 0x7FFEE00E7CF0, 0x0) = 0 0
369389fcntl(0x3, 0x32, 0x7FFEE00E9CB0) = 0 0
370390close(0x3) = 0 0
371391stat64("/Users/[user]/LAZPROJECTS/fpc_dynamic_lib\0", 0x7FFEE00E7C60, 0x0) = 0 0
372392stat64("/Users/[user]/LAZPROJECTS/fpc_dynamic_lib\0", 0x7FFEE00E7EF8, 0x0) = 0 0
373393getattrlist("/Users/[user]/LAZPROJECTS/fpc_dynamic_lib/libtest.dylib\0", 0x117102048, 0x7FFEE00E9850) = -1 Err#2
374394stat64("/Users/[user]/LAZPROJECTS/fpc_dynamic_lib/libtest.dylib\0", 0x7FFEE00E96E0, 0x0) = -1 Err#2
375395stat64("/Users/[user]/LAZPROJECTS/fpc_dynamic_lib/libtest.dylib\0", 0x7FFEE00E9B20, 0x0) = -1 Err#2
376396...
377397```
378398
379399The highlighted lines above show all the places the the system looked for the missing dynamic library.
380400
381401## Standard locations for dynamic libraries
382402
383403While 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.
384404
385405If 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:
386406
387407- LD_LIBRARY_PATH
388408- DYLD_LIBRARY_PATH
389409- DYLD_FALLBACK_LIBRARY_PATH
390410
391411Note: 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.
392412
393413For 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.
394414
395415## Logging dynamic loader events
396416
397417As 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:
398418
399419| Environment variable | Description |
400420|----|----|
401421| DYLD_PRINT_LIBRARIES | Logs when images are loaded. |
402422| DYLD_PRINT_LIBRARIES_POST_LAUNCH | Logs when images are loaded as a result of a dlopen call. Includes a dynamic libraries’ dependent libraries. |
403423| DYLD_PRINT_APIS | Logs the invocation that causes the dynamic loader to return the address of a symbol. |
404424| DYLD_PRINT_STATISTICS | Logs statistical information on an application’s launch process, such as how many images were loaded, when the application finishes launching. |
405425| DYLD_PRINT_INITIALIZERS | Logs when the dynamic loader calls initializer and finalizer functions. |
406426| DYLD_PRINT_SEGMENTS | Logs when the dynamic loader maps a segment of a dynamic library to the current process’s address space. |
407427| DYLD_PRINT_BINDINGS | Logs when the dynamic loader binds an undefined external symbol with its definition. |
408428
409429For example, Open an Applications \> Utilities \> Terminal - note the shell being used below is `tcsh`:
410430
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`
431+```text
432+$ setenv DYLD_PRINT_STATISTICS
433+$ ./my_app
434+Total pre-main time: 13.41 milliseconds (100.0%)
435+ dylib loading time: 6.39 milliseconds (47.6%)
436+ rebase/binding time: 0.97 milliseconds (7.2%)
437+ ObjC setup time: 0.46 milliseconds (3.4%)
438+ initializer time: 5.40 milliseconds (40.2%)
439+ slowest intializers :
440+ libSystem.B.dylib : 1.19 milliseconds (8.9%)
441+ libobjc.A.dylib : 0.47 milliseconds (3.5%)
442+ CoreFoundation : 2.35 milliseconds (17.5%)
443+ AppKit : 0.61 milliseconds (4.6%)
444+$ unsetenv DYLD_PRINT_STATISTICS
445+```
424446
425447For the default macOS shell `zsh`, use `export DYLD_PRINT_STATISTICS=1` and `unset DYLD_PRINT_STATISTICS` to set and unset an environment variable.
426448
427449## See also
428450
429451- [[Application Bundle]] - structure of a macOS application
430452
431453- [[macOS Static Libraries]] for the same FPC library being used as a static library.
432454
433455- [[macOS Frameworks]] - structured directories for dynamic libraries and associated resources.
434456
435457- [[macOS Programming Tips|macOS Libraries]].
436458
437459- [[shared library|FPC Shared Libraries]].
438460
439461- [[macOS Big Sur changes for developers|Big Sur's built-in dynamic linker cache]].
440462
441463## External links
442464
443465- [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).
444466
445467---
446468*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