Sign in Join
Wiki copytemporary

Accessing macOS System Information: what changed

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

OldNewLine
11## Overview
22
33macOS provides four main ways to access system information:
44
55- Sysctl interface - An interface used by Unix and Unix-like operating systems to access system hardware and kernel configuration attributes.
66- NSProcessInfo class - A class in the Foundation framework which gives access to a collection of information about the current process.
77- Gestalt Manager - Allows applications to query the running system configuration. **Deprecated** after 10.7 (Lion) in favour of the NSProcessInfo class or the SysCtl interface.
88- IOKit framework - A framework that implements non-kernel access to I/O Kit objects (drivers and nubs) through the device-interface mechanism.
99
1010These are described below with code examples.
1111
1212## Sysctl
1313
1414Sysctl provides an interface that allows you to read and, with appropriate privileges, set many kernel attributes in macOS (and Linux and the BSDs). This provides a wealth of information detailing system hardware and configuration attributes which can be useful when debugging or simply optimising your application (eg to take advantage of threads on multi-core CPU systems).
1515
1616The '/usr/local/share/fpcsrc/rtl/darwin/sysctlh.inc' source file is a partial translation of the macOS '/usr/include/sys/sysctl.h' file. For details of the sysctls available, start a Terminal and type 'man sysctl'. Typing 'sysctl-a \| more' in a Terminal will list all the sysctls.
1717
1818### FPsysctl()
1919
2020> **Note:** The function definition below for FPsysctl() is for FPC 3.2.0 and later - For FPC 3.0.4 and earlier the first argument is *pchar* and not *pcint*. Adjust the code examples accordingly.
2121
2222The fpSysCtl() function allows you to retrieve system information. The data returned from the function comprises integers (integer and int64), strings (AnsiStrings) and C structures (records). The function is defined in `/usr/local/share/fpcsrc/rtl/bsd/sysctl.pp` as:
2323
2424``` pascal
2525function FPsysctl (Name: pcint; namelen:cuint; oldp:pointer;oldlenp:psize_t; newp:pointer;newlen:size_t):cint; cdecl; external name 'sysctl';
2626```
2727
2828*name* -- a pointer to a Management Information Base (MIB) array of integers. Each element of this array must contain the correct value to read or write the particular system attribute.
2929
3030*namelen* -- the length of the array of integers in name.
3131
3232Note: if the old attribute value is not required, the next two variables can be set to Nil and 0.
3333
3434*oldp* -- a pointer to the buffer into which the value is copied.
3535
3636*oldlenp* -- the size of the oldp buffer.
3737
3838Note: if the new attribute value is not being set, the next two variables should be set to Nil and 0.
3939
4040*newp* -- a pointer to the buffer containing the new value to be set.
4141
4242*newlen* -- the size of the newp buffer.
4343
4444The size of the MIB array depends on the data to be read or written. The first element indicates the level of the information and the subsequent elements indicate the value to read. The top level names are:
4545
46-```
46+```text
4747 --------------------------------------------------------------
4848 Name Next level names Description
4949 are found in
5050 --------------------------------------------------------------
5151 CTL_DEBUG sys/sysctl.h Debugging
5252 CTL_VFS sys/mount.h File system
5353 CTL_HW sys/sysctl.h Generic CPU, I/O
5454 CTL_KERN sys/sysctl.h High kernel limits
5555 CTL_MACHDEP sys/sysctl.h Machine dependent
5656 CTL_NET sys/socket.h Networking
5757 CTL_USER sys/sysctl.h User-level
5858 CTL_VM sys/resources.h Virtual memory
5959 --------------------------------------------------------------
6060```
6161
6262Let's look at CTL_HW. The subsequent elements are found in the system sysctl.h file and are:
6363
6464``` pascal
6565 -------------------------------------------------------------------------------
6666 Name Value Description
6767 -------------------------------------------------------------------------------
6868 #define HW_MACHINE 1 /* string: machine class */
6969 #define HW_MODEL 2 /* string: specific machine model */
7070 #define HW_NCPU 3 /* int: number of cpus */
7171 #define HW_BYTEORDER 4 /* int: machine byte order */
7272 #define HW_PHYSMEM 5 /* int: total memory */
7373 #define HW_USERMEM 6 /* int: non-kernel memory */
7474 #define HW_PAGESIZE 7 /* int: software page size */
7575 #define HW_DISKNAMES 8 /* strings: disk drive names */
7676 #define HW_DISKSTATS 9 /* struct: diskstats[] */
7777 #define HW_EPOCH 10 /* int: 0 for Legacy, else NewWorld */
7878 #define HW_FLOATINGPT 11 /* int: has HW floating point? */
7979 #define HW_MACHINE_ARCH 12 /* string: machine architecture */
8080 #define HW_VECTORUNIT 13 /* int: has HW vector unit? */
8181 #define HW_BUS_FREQ 14 /* int: Bus Frequency */
8282 #define HW_CPU_FREQ 15 /* int: CPU Frequency */
8383 #define HW_CACHELINE 16 /* int: Cache Line Size in Bytes */
8484 #define HW_L1ICACHESIZE 17 /* int: L1 I Cache Size in Bytes */
8585 #define HW_L1DCACHESIZE 18 /* int: L1 D Cache Size in Bytes */
8686 #define HW_L2SETTINGS 19 /* int: L2 Cache Settings */
8787 #define HW_L2CACHESIZE 20 /* int: L2 Cache Size in Bytes */
8888 #define HW_L3SETTINGS 21 /* int: L3 Cache Settings */
8989 #define HW_L3CACHESIZE 22 /* int: L3 Cache Size in Bytes */
9090 #define HW_TB_FREQ 23 /* int: Bus Frequency */
9191 #define HW_MEMSIZE 24 /* uint64_t: physical ram size */
9292 #define HW_AVAILCPU 25 /* int: number of available CPUs */
9393 #define HW_MAXID 26 /* number of valid hw ids */
9494 -------------------------------------------------------------------------------
9595```
9696
9797Armed with this information, we can now find out the number of CPUs:
9898
9999``` pascal
100100// The full source code for this example is available from:
101101// https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/num_cpu.pas
102102...
103103Uses
104104 SysUtils, SysCtl, Unix;
105105...
106106function NumberOfCPU: Integer;
107107
108108var
109109 mib: array[0..1] of Integer;
110110 status : Integer;
111111 len : size_t;
112112
113113begin
114114 mib[0] := CTL_HW;
115115 mib[1] := HW_NCPU;
116116
117117 len := SizeOf(Result);
118118 status := fpSysCtl(PCInt(@mib), Length(mib), @Result, @len, Nil, 0);
119119 if status <> 0 then RaiseLastOSError;
120120end;
121121```
122122
123123The above code returns the number of CPUs in an integer; you need to check the C header file to determine what is being returned (an integer, int64, string, struct/record).
124124
125125There is a possible issue with the above because if you have, for example, an Intel CPU with hyperthreading. One guess. Yes, it will return the total number of threads and not the number of CPUs or even the number of physical cores. HW_NCPU is pretty much deprecated these days as a useful attribute. Instead, we can retrieve the number of CPU packages, physical CPUs (cores) or logical CPUs (the same as HW_NCPU). To do so requires the use of a second function 'FPsysctlbyname'.
126126
127127However, before we move on, let's look at retrieving a string with FPsysctl() which is a little different than retrieving an integer.
128128
129129``` pascal
130130// The full source code for this example is available from:
131131// https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/hardware_model.pas
132132...
133133Uses
134134 SysUtils, SysCtl, Unix;
135135...
136136function HWmodel : AnsiString;
137137
138138var
139139 mib : array[0..1] of Integer;
140140 status : Integer;
141141 len : size_t;
142142 p : PChar;
143143
144144begin
145145 mib[0] := CTL_HW;
146146 mib[1] := HW_MODEL;
147147
148148 status := fpSysCtl(PCInt(@mib), Length(mib), Nil, @len, Nil, 0);
149149 if status <> 0 then RaiseLastOSError;
150150
151151 GetMem(p, len);
152152
153153 try
154154 status := fpSysCtl(PCInt(@mib), Length(mib), p, @len, Nil, 0);
155155 if status <> 0 then RaiseLastOSError;
156156 Result := p;
157157 finally
158158 FreeMem(p);
159159 end;
160160end;
161161```
162162
163163Notice that this time we needed two calls to FPsysctl() - one to find out the size of the buffer required to hold the string value, and the second to actually retrieve the string value into that buffer. On my Apple computer this returns the string "Macmini8,1" - the 2018 Mac mini.
164164
165165No, we're not quite ready to move on yet... we still need to see how to retrieve information in a C structure (Pascal record).
166166
167167``` pascal
168168// The full source code for this example is available from:
169169// https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/swap_usage.pas
170170...
171171Uses
172172 SysUtils, SysCtl, Unix;
173173...
174174function SwapUsage: String;
175175
176176type
177177 swapinfo = record
178178 xsu_total : Int64;
179179 xsu_avail : Int64;
180180 xsu_used : Int64;
181181 xsu_pagesize : Integer;
182182 xsu_encrypted: Boolean;
183183end;
184184
185185const
186186 VM_SWAPUSAGE = 5;
187187
188188var
189189 mib : array[0..1] of Integer;
190190 status : Integer;
191191 len : size_t;
192192 swap : swapinfo;
193193 SwapEncrypted: String;
194194
195195begin
196196 mib[0] := CTL_VM;
197197 mib[1] := VM_SWAPUSAGE;
198198 swap := Default(swapinfo);
199199
200200 FillChar(swap, sizeof(swap), 0);
201201 len := sizeof(swap);
202202
203203 status := fpSysCtl(PCInt(@mib), Length(mib), @swap, @len, Nil, 0);
204204 if status <> 0 then RaiseLastOSError;
205205
206206 if(swap.xsu_encrypted = true) then
207207 SwapEncrypted := 'Yes' else SwapEncrypted := 'No';
208208
209209 Result := 'Swap total: ' + FloatToStr(Round(swap.xsu_total /1024 /1024)) + ' MB'
210210 + LineEnding + 'Swap used: ' + FloatToStr(Round(swap.xsu_used /1024 /1024)) + ' MB'
211211 + LineEnding + 'Swap free: ' + FloatToStr(Round(swap.xsu_avail /1024 /1024)) + ' MB'
212212 + LineEnding + 'Swap page size: ' + IntToStr(swap.xsu_pagesize) + ' bytes'
213213 + LineEnding + 'Swap encrypted: ' + SwapEncrypted + LineEnding;
214214end;
215215```
216216
217217> **Note:** If your computer has not used any swap, then the results for Swap total, used and free will all be zero.
218218
219219On my computer this returned:
220220
221-```
221+```text
222222 Swap total: 1024 MB
223223 Swap used: 191 MB
224224 Swap free: 833 MB
225225 Swap page size: 4096 bytes
226226 Swap encrypted: Yes
227227```
228228
229229The swapinfo record was pretty easily translated from the C structure found in the system `sysctl.h` file:
230230
231231``` c
232232struct xsw_usage {
233233 u_int64_t xsu_total;
234234 u_int64_t xsu_avail;
235235 u_int64_t xsu_used;
236236 u_int32_t xsu_pagesize;
237237 boolean_t xsu_encrypted;
238238};
239239```
240240
241241We also defined the MIB constant VM_SWAPUSAGE by looking at the system `sysctl.h` file. Why? Because Free Pascal 3.2.0 and earlier are missing this identifier (fixed in FPC 3.2.2). So, we looked it up and found:
242242
243243``` c
244244#define VM_SWAPUSAGE 5 /* total swap usage */
245245```
246246
247247Finally, yes, it is time to move on to the FPsysctlbyname() function.
248248
249249### FPsysctlbyname
250250
251251The FPsysctlbyname function is defined in '/usr/local/share/fpcsrc/rtl/bsd/sysctl.pp' as:
252252
253253``` pascal
254254function FPsysctlbyname (Name: pchar; oldp:pointer;oldlenp:psize_t; newp:pointer;newlen:size_t):cint; cdecl; external name 'sysctlbyname';
255255```
256256
257257There. Not really very different from the FPsysctl() function. Is that a sigh of relief? So let's look at finally retrieving the number of physical CPU cores.
258258
259259``` pascal
260260// Full source code for this example is available from:
261261// https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/num_cores.pas
262262...
263263{$mode objfpc}
264264...
265265
266266Uses
267267 SysCtl, SysUtils, Unix;
268268
269269function NumberOfCores: Integer;
270270
271271var
272272 status : Integer;
273273 len : size_t;
274274
275275begin
276276 len := SizeOf(Result);
277277
278278 status := fpSysCtlByName('hw.physicalcpu', @Result, @len, Nil, 0);
279279 if status <> 0 then RaiseLastOSError;
280280end;
281281```
282282
283283On my 2018 Mac mini this returns the correct number of cores: 6. Where do you find the special incantation **hw.physicalcpu** ? Yes, in the system `sysctl.h` file. Here's a brief excerpt of some of the hardware selectors:
284284
285285``` pascal
286286 These are the support HW selectors for sysctlbyname. Parameters that are byte counts or frequencies are 64 bit numbers.
287287 All other parameters are 32 bit numbers.
288288
289289 hw.memsize - The number of bytes of physical memory in the system.
290290 hw.ncpu - The maximum number of processors that could be available this boot.
291291 Use this value for sizing of static per processor arrays; i.e. processor load statistics.
292292 hw.activecpu - The number of processors currently available for executing threads.
293293 Use this number to determine the number threads to create in SMP aware applications.
294294 This number can change when power management modes are changed.
295295 hw.physicalcpu - The number of physical processors available in the current power management mode.
296296 hw.physicalcpu_max - The maximum number of physical processors that could be available this boot
297297 hw.logicalcpu - The number of logical processors available in the current power management mode.
298298 hw.logicalcpu_max - The maximum number of logical processors that could be available this boot
299299 hw.tbfrequency - This gives the time base frequency used by the OS and is the basis of all timing services.
300300 In general is is better to use mach's or higher level timing services, but this value
301301 is needed to convert the PPC Time Base registers to real time.
302302 hw.cpufrequency - These values provide the current, min and max cpu frequency. The min and max are for
303303 hw.cpufrequency_max - all power management modes. The current frequency is the max frequency in the current mode.
304304 hw.cpufrequency_min - All frequencies are in Hz.
305305 hw.busfrequency - These values provide the current, min and max bus frequency. The min and max are for
306306 hw.busfrequency_max - all power management modes. The current frequency is the max frequency in the current mode.
307307 hw.busfrequency_min - All frequencies are in Hz.
308308 hw.cputype - These values provide the mach-o cpu type and subtype. A complete list is in <mach/machine.h>
309309 hw.cpusubtype - These values should be used to determine what processor family the running cpu is from so that
310310 the best binary can be chosen, or the best dynamic code generated. They should not be used
311311 to determine if a given processor feature is available.
312312 hw.cputhreadtype - This value will be present if the processor supports threads. Like hw.cpusubtype this selector
313313 should not be used to infer features, and only used to name the processors thread architecture.
314314 The values are defined in <mach/machine.h>
315315 hw.byteorder - Gives the byte order of the processor. 4321 for big endian, 1234 for little.
316316 hw.pagesize - Gives the size in bytes of the pages used by the processor and VM system.
317317 hw.cachelinesize - Gives the size in bytes of the processor's cache lines.
318318 This value should be use to control the strides of loops that use cache control instructions
319319 like dcbz, dcbt or dcbst.
320320 hw.l1dcachesize - These values provide the size in bytes of the L1, L2 and L3 caches. If a cache is not present
321321 hw.l1icachesize - then the selector will return an error.
322322 hw.l2cachesize -
323323 hw.l3cachesize -
324324 hw.packages - Gives the number of processor packages.
325325```
326326
327327If you prefer, you can instead use the `sysctl` command line utility in a Terminal to list all the hw selectors with the `sysctl hw` command.
328328
329329Retrieving a string value, rather than an integer, is again reminiscent of FPsysctl(). Two calls to FPsysctlbyname() are needed: (1) to find out the size of the buffer to hold the string to be returned and (2) to return the string value into that buffer. Don't forget to allocate memory for the buffer before the second call!
330330
331331``` pascal
332332// Full source code for this example is available from:
333333// https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/brand_cpu.pas
334334...
335335{$mode objfpc}
336336...
337337
338338Uses
339339 SysCtl, SysUtils, Unix;
340340
341341function BrandOfCPU: AnsiString;
342342
343343var
344344 status : Integer;
345345 len : size_t;
346346 p : PChar;
347347
348348begin
349349 status := fpSysCtlByName('machdep.cpu.brand_string', Nil, @len, Nil, 0);
350350 if status <> 0 then RaiseLastOSError;
351351
352352 GetMem(p, len);
353353
354354 try
355355 status := fpSysCtlByName('machdep.cpu.brand_string', p, @len, Nil, 0);
356356 if status <> 0 then RaiseLastOSError;
357357 Result := p;
358358 finally
359359 FreeMem(p);
360360 end;
361361end;
362362```
363363
364364On my 2018 Mac mini computer, this returns the very informative string: *Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz*. On my 2020 Mac mini computer, this returns the somewhat less informative string: *Apple M1*.
365365
366366Oh, you noticed, I changed the top level identifier from **hw** to **machdep** (machine dependent). Again, consult `sysctl.h` or use the `sysctl` command line utility in a Terminal.
367367
368368``` pascal
369369// Full source code for this example is available from:
370370// https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/os_version.pas
371371
372372// Note: The kern.osproductversion attribute was added to the Darwin kernel in December 2018
373373// https://github.com/apple/darwin-xnu/commit/5bbb823c13f3ab1ab58878f96b35433a29882676
374374
375375Program os_version;
376376
377377{$mode objfpc}
378378
379379Uses
380380 SysCtl, SysUtils, Unix;
381381
382382function OS_version : AnsiString;
383383
384384var
385385 status : Integer;
386386 len : size_t;
387387 p : PChar;
388388
389389begin
390390 status := fpSysCtlByName('kern.osproductversion', Nil, @len, Nil, 0);
391391 if status <> 0 then RaiseLastOSError;
392392
393393 GetMem(p, len);
394394
395395 try
396396 status := fpSysCtlByName('kern.osproductversion', p, @len, Nil, 0);
397397 if status <> 0 then RaiseLastOSError;
398398 Result := p;
399399 finally
400400 FreeMem(p);
401401 end;
402402end;
403403
404404begin
405405 WriteLn(OS_version);
406406end.
407407```
408408
409409The above FPC example program will return the operating system version. On my M1 processor Mac mini this was "11.2.3" (Big Sur).
410410
411411### Last words
412412
413413For the optimised, performance fiends out there, the man page for these functions notes that the FPsysctl() function runs in about a third the time as the same request made via the FPsysctlbyname() function.
414414
415415It should be noted that many of the sysctl attributes are read-only, but there are also quite a few that can be changed. I've left changing a sysctl attribute as an exercise for you dear reader. It should be simple if you study the definitions of the function parameters, paying particular attention to newp and newlen.
416416
417417## NSProcessinfo
418418
419419Each macOS process has a single, shared NSProcessInfo object, known as a process information agent. This process information agent can return information such as arguments, environment variables, host name, and process name. The processInfo class method returns the shared agent for the current process—that is, the process whose object sent the message.
420420
421421### Properties
422422
423423| Property | Description |
424424|----|----|
425425| processInfo | Returns the process information agent for the process. An NSProcessInfo object is created the first time this method is invoked, and that same object is returned on each subsequent invocation. |
426426| arguments | Array of strings with the command-line arguments for the process. |
427427| environment | The variable names (keys) and their values in the environment from which the process was launched. |
428428| globallyUniqueString | Global unique identifier for the process. The global ID for the process includes the host name, process ID, and a time stamp, which ensures that the ID is unique for the network. This property generates a new string each time its getter is invoked, and it uses a counter to guarantee that strings created from the same process are unique. |
429429| processIdentifier | The identifier of the process (often called process ID or PID). |
430430| processName | The name of the process. |
431431| userName | Returns the account name of the current user. macOS 10.12+ |
432432| fullUserName | Returns the full name of the current user. macOS 10.12+ |
433433| hostName | The name of the host computer on which the process is executing. |
434434| operatingSystemVersionString | A string containing the version of the operating system on which the process is executing. macOS 10.2+ |
435435| operatingSystemVersion | The version of the operating system on which the process is executing. macOS 10.10+ |
436436| processorCount | The number of processing cores available on the computer. Equivalent to: `sysctl -n hw.ncpu`. macOS 10.5+ |
437437| activeProcessorCount | The number of active processing cores available on the computer. Equivalent to: `sysctl -n hw.logicalcpu`. macOS 10.5+ |
438438| physicalMemory | The amount of physical memory on the computer in bytes. macOS 10.5+ |
439439| systemUptime | The amount of time in seconds that the system has been awake since the last time it was restarted. macOS 10.6+ |
440440| thermalState | The current thermal state of the system: Nominal, Fair, Serious or Critical. macOS 10.10.3+ |
441441| automaticTerminationSupportEnabled | A Boolean value indicating whether the app supports automatic termination. Currently, setting this property to False has no effect. macOS 10.7+ |
442442| | |
443443
444444### Methods
445445
446446| Method | Description |
447447|----|----|
448448| isOperatingSystemAtLeastVersion | Returns a Boolean value indicating whether the version of the operating system on which the process is executing is the same or later than the given version. macOS 10.10+ |
449449
450450### Example code
451451
452452``` pascal
453453unit Unit1;
454454
455455{$mode objfpc}{$H+}
456456{$modeswitch objectivec1}
457457
458458interface
459459
460460uses
461461 Classes, SysUtils, Forms, Dialogs, StdCtrls,
462462 ctypes, // needed for cint
463463 typinfo, // needed for GetEnumName
464464 CocoaAll; // needed for NS...
465465
466466type
467467 // From HERE...
468468 NSProcessInfoThermalState = (
469469 Nominal, // within the normal range; no corrective action is required.
470470 Fair, // thermal state is slightly elevated and the fan is audible.
471471 Serious, // fan is at maximum speed; performance may be affected;
472472 // applications should reduce use of CPU, GPU, and any I/O.
473473 Critical); // thermal state severely affects performance; need immediate cooling;
474474 // applications should reduce use of CPU, GPU, and any I/O to minimum
475475 // required for user interaction; stop using peripherals such as cameras.
476476
477477 NSOperatingSystemVersion = record
478478 majorVersion: NSInteger;
479479 minorVersion: NSInteger;
480480 patchVersion: NSInteger;
481481 end;
482482
483483 NSProcessInfo = objcclass external (NSObject)
484484 private
485485 environment_: NSDictionary;
486486 arguments_: NSArray;
487487 hostName_: NSString;
488488 name: NSString;
489489 automaticTerminationOptOutCounter: NSInteger;
490490 public
491491 class function processInfo: NSProcessInfo; message 'processInfo';
492492 function environment: NSDictionary; message 'environment';
493493 function arguments: NSArray; message 'arguments';
494494 function hostName: NSString; message 'hostName';
495495 procedure setProcessName(newValue: NSString); message 'setProcessName:';
496496 function processName: NSString; message 'processName';
497497 function processIdentifier: cint; message 'processIdentifier';
498498 function globallyUniqueString: NSString; message 'globallyUniqueString';
499499 function operatingSystemVersionString: NSString; message 'operatingSystemVersionString';
500500 function userName: NSString; message 'userName';
501501 function fullUserName: NSString; message 'fullUserName';
502502 function operatingSystemVersion: NSOperatingSystemVersion; message 'operatingSystemVersion';
503503 function processorCount: NSUInteger; message 'processorCount';
504504 function activeProcessorCount: NSUInteger; message 'activeProcessorCount';
505505 function physicalMemory: culonglong; message 'physicalMemory';
506506 function isOperatingSystemAtLeastVersion (os_version: NSOperatingSystemVersion): Boolean; message 'isOperatingSystemAtLeastVersion:';
507507 function systemUptime: NSTimeInterval; message 'systemUptime';
508508 function thermalState: NSInteger; message 'thermalState';
509509 {procedure disableSuddenTermination; message 'disableSuddenTermination';
510510 procedure enableSuddenTermination; message 'enableSuddenTermination';
511511 procedure disableAutomaticTermination (reason: NSString); message 'disableAutomaticTermination:';
512512 procedure enableAutomaticTermination (reason: NSString); message 'enableAutomaticTermination:';}
513513 procedure setAutomaticTerminationSupportEnabled(newValue: Boolean); message 'setAutomaticTerminationSupportEnabled:';
514514 function automaticTerminationSupportEnabled: Boolean; message 'automaticTerminationSupportEnabled';
515515 end;
516516 // ... To HERE is ONLY NEEDED if you are using FPC 3.0.4 or earlier. FPC 3.2.0 does not need this section.
517517
518518 { TForm1 }
519519
520520 TForm1 = Class(TForm)
521521 Button1: TButton;
522522 Memo1: TMemo;
523523 procedure Button1Click(Sender: TObject);
524524 private
525525
526526 public
527527
528528 end;
529529
530530var
531531 Form1: TForm1;
532532
533533implementation
534534
535535{$R *.lfm}
536536
537537{ TForm1 }
538538
539539procedure ProcessInfo;
540540var
541541 thermalState : String;
542542 myDictionary : NSDictionary;
543543 i : Byte = 0;
544544 minOsVer : NSOperatingSystemVersion;
545545begin
546546 Form1.Memo1.Lines.Clear;
547547
548548 Form1.Memo1.Lines.Add('Processor count: '
549549 + IntToStr(NSProcessInfo.ProcessInfo.processorCount)
550550 + LineEnding + 'Active processors: '
551551 + IntToStr(NSProcessInfo.ProcessInfo.activeProcessorCount)
552552 + LineEnding + 'Physical memory: '
553553 + FloatToStr(NSProcessInfo.ProcessInfo.physicalMemory / 1024 / 1024 / 1024) + 'GB'
554554 + LinEending + 'Process name: '
555555 + NSProcessInfo.ProcessInfo.processName.UTF8String
556556 + LinEending + 'PID: '
557557 + IntToStr(NSProcessInfo.ProcessInfo.processIdentifier)
558558 + LineEnding + 'Uptime: '
559559 + FormatFloat('0.00', NSProcessInfo.ProcessInfo.systemUptime / 60 / 60 / 24) + ' days'
560560 + LineEnding + 'Operating system: '
561561 + NSProcessInfo.ProcessInfo.operatingSystemVersionString.UTF8String
562562 + LineEnding
563563 + 'Major: ' + IntToStr(NSProcessInfo.ProcessInfo.OperatingSystemVersion.majorVersion)
564564 + ' Minor: ' + IntToStr(NSProcessInfo.ProcessInfo.OperatingSystemVersion.minorVersion)
565565 + ' Patch: ' + IntToStr(NSProcessInfo.ProcessInfo.OperatingSystemVersion.patchVersion)
566566 + LineEnding + 'Hostname: '
567567 + NSProcessInfo.ProcessInfo.hostName.UTF8String
568568 + LineEnding + 'Globally unique string: '
569569 + NSProcessInfo.ProcessInfo.globallyUniqueString.UTF8String
570570 + LineEnding + 'Username: '
571571 + NSProcessInfo.ProcessInfo.userName.UTF8String
572572 + LineEnding + 'Full Username: '
573573 + NSProcessInfo.ProcessInfo.fullUserName.UTF8String
574574 + LineEnding
575575 );
576576
577577 myDictionary := NSProcessInfo.ProcessInfo.environment;
578578 for i := 0 to (myDictionary.count - 1) do
579579 begin
580580 Form1.Memo1.Lines.Add('Env Var: '
581581 + myDictionary.allKeys.objectAtIndex(i).UTF8String
582582 + ' = '
583583 + myDictionary.allValues.objectAtIndex(i).UTF8String
584584 );
585585 end;
586586
587587 ThermalState := GetEnumName(TypeInfo(NSProcessInfoThermalState), Ord(NSProcessInfo.ProcessInfo.thermalState));
588588 Form1.Memo1.Lines.Add(LineEnding + 'Thermal state: ' + ThermalState
589589 );
590590
591591 // Setup minimum OS version
592592 minOsVer.majorVersion:= 10;
593593 minOsVer.minorVersion:= 14;
594594 minOsVer.patchVersion:= 6;
595595
596596 if(NSProcessInfo.ProcessInfo.isOperatingSystemAtLeastVersion(minOSVer)) then
597597 Form1.Memo1.Lines.Add(LineEnding + 'OS version is at least 10.14.6' + LineEnding)
598598 else
599599 Form1.Memo1.Lines.Add(LineEnding + 'OS version is NOT at least 10.14.6' + LineEnding)
600600end;
601601
602602procedure TForm1.Button1Click(Sender: TObject);
603603begin
604604 processInfo;
605605end;
606606
607607end.
608608```
609609
610610## Gestalt Manager
611611
612612Despite Apple's deprecation of the Gestalt Manager in favour of the NSProcessInfo class or the SysCtl Interface, there are still occasions when you will need to use it. For example, the NSProcessInfo instance property `OperatingSystemVersion` is only available from macOS 10.10 (Yosemite) onwards according to Apple's documentation, although it seems to also work in macOS 10.9 (Mavericks).
613613
614614So what do you do if you want to check the version of the operating system across all versions back to Mac OS X 10.0 (Cheetah)? The example code below holds the solution.
615615
616616### Example: Retrieving OS version from Mac OS X 10.0+
617617
618618``` pascal
619619unit Unit1;
620620
621621{$mode objfpc}{$H+}
622622{$modeswitch objectivec2}
623623
624624interface
625625
626626uses
627627 Classes, SysUtils, Forms, Dialogs, StdCtrls, ExtCtrls,
628628 MacOSAll,
629629 CocoaAll;
630630
631631type
632632
633633 { TForm1 }
634634
635635 TForm1 = Class(TForm)
636636 Memo1: TMemo;
637637 Button1: TButton;
638638 procedure Button1Click(Sender: TObject);
639639 private
640640 public
641641
642642 end;
643643
644644var
645645 Form1: TForm1;
646646 counter: Int64;
647647
648648implementation
649649
650650{$R *.lfm}
651651
652652{ TForm1 }
653653
654654procedure ProcessInfo;
655655begin
656656 Form1.Memo1.Lines.Clear;
657657
658658 Form1.Memo1.Lines.Add('macOS version: '
659659 + IntToStr(NSProcessInfo.ProcessInfo.operatingSystemVersion.majorVersion)
660660 + '.'
661661 + IntToStr(NSProcessInfo.ProcessInfo.operatingSystemVersion.minorVersion)
662662 + '.'
663663 + IntToStr(NSProcessInfo.ProcessInfo.operatingSystemVersion.patchVersion)
664664 );
665665end;
666666
667667Procedure GestaltInfo;
668668var
669669 majorVersion: LongInt = 0;
670670 minorVersion: LongInt = 0;
671671 patchVersion: LongInt = 0;
672672begin
673673 Form1.Memo1.Lines.Clear;
674674 Form1.Memo1.Lines.Add('Trying another way...');
675675
676676 Gestalt(gestaltSystemVersionMajor, majorVersion);
677677 Gestalt(gestaltSystemVersionMinor, minorVersion);
678678 Gestalt(gestaltSystemVersionBugFix, patchVersion);
679679
680680 Form1.Memo1.Lines.Add('macOS version: ' + IntToStr(majorVersion) + '.' +
681681 IntToStr(minorVersion) + '.' + IntToStr(patchVersion));
682682end;
683683
684684procedure TForm1.Button1Click(Sender: TObject);
685685var
686686 aSEL : SEL;
687687begin
688688 // Check whether OS version responds to this selector
689689 aSEL := objcselector('operatingSystemVersion');
690690 if NSProcessInfo.processInfo.respondsToSelector(aSEL) then
691691 ProcessInfo
692692 // Otherwise fallback to the deprecated Gestalt Manager
693693 else
694694 GestaltInfo;
695695end;
696696
697697end.
698698```
699699
700700## IOKit
701701
702702IOKit is Apple’s object-oriented framework for developing device drivers for macOS. While the main use of the IOKit framework is for developers creating device drivers that are to be resident in the kernel, it is also of use to application developers who can using an IOKit device interface to communicate with the Apple hardware.
703703
704704While the focus of this section is on accessing macOS system information, it should be noted that the IOKit framework has been deprecated by Apple in macOS 10.15 Catalina in favour of the DriverKit framework which debuted in that version of macOS. DriverKit provides a fully modernized replacement for IOKit to create device drivers. System extensions and drivers built with DriverKit run in user space, where they can't compromise the security or stability of macOS.
705705
706706### Example: Retrieving the platform serial number
707707
708708``` pascal
709709program project1;
710710
711711{$mode objfpc}{$H+}
712712{$modeswitch cvar}
713713{$linkframework IOKit}
714714
715715uses
716716 ctypes, Classes, MacOSAll;
717717{ you can add units after this };
718718
719719type
720720 kern_return_t = cint; // if you use "integer" then you don't need "ctypes" unit
721721 io_object_t = mach_port_t;
722722 io_registry_entry_t = io_object_t;
723723
724724 io_string_t = array [0..511] of ansichar;
725725 IOOptionBits = UInt32;
726726
727727const
728728 kIOPlatformSerialNumber = 'IOPlatformSerialNumber';
729729
730730var
731731 kIOMasterPortDefault:mach_port_t; cvar; external;
732732
733733function IORegistryEntryFromPath( masterPort: mach_port_t; const path: io_string_t): io_registry_entry_t; cdecl; external;
734734function IORegistryEntryCreateCFProperty(entry: io_registry_entry_t; key: CFStringRef; allocator:CFAllocatorRef; options: IOOptionBits): CFTypeRef; cdecl; external;
735735function IOObjectRelease(entry: io_registry_entry_t): kern_return_t; cdecl; external;
736736
737737function get_platform_serial_num: string;
738738var
739739 ioRegistryRoot :io_registry_entry_t;
740740 serialnumCf :CFStringRef;
741741 l :integer;
742742begin
743743 ioRegistryRoot := IORegistryEntryFromPath(kIOMasterPortDefault, 'IOService:/');
744744 serialnumCf := CFStringRef (IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformSerialNumber), kCFAllocatorDefault, 0));
745745 IOObjectRelease(ioRegistryRoot);
746746 SetLength(Result, 32);
747747 CFStringGetCString(serialnumCf, @Result[1], length(Result), kCFStringEncodingMacRoman);
748748 CFRelease(serialnumCf);
749749 l:=StrLen(@Result[1]);
750750 SetLength(Result, l);
751751end;
752752
753753begin
754754 WriteLn('Platform serial number: ', get_platform_serial_num);
755755end.
756756```
757757
758758### Example: Retrieving the platform UUID
759759
760760A UUID (Universally Unique Identifier) is a 16-byte long number. It is normally expressed in a standard format of characters representing hexadecimal digits. UUIDs were formerly known as Globally Unique Identifiers (GUIDs), but are now officially Universally Unique Identifiers, or UUIDs, and are governed by Open Software Foundation, ISO/IEC, and IETF standards.
761761
762762``` pascal
763763program project1;
764764
765765{$mode objfpc}{$H+}
766766{$modeswitch cvar}
767767{$linkframework IOKit}
768768
769769uses
770770 ctypes, Classes, MacOSAll
771771{ you can add units after this };
772772
773773type
774774 kern_return_t = cint; // if you use "integer" then you don't need "ctypes" unit
775775 io_object_t = mach_port_t;
776776 io_registry_entry_t = io_object_t;
777777
778778 io_string_t = array [0..511] of ansichar;
779779 IOOptionBits = UInt32;
780780
781781const
782782 kIOPlatformUUIDKey = 'IOPlatformUUID';
783783
784784var
785785 kIOMasterPortDefault:mach_port_t; cvar; external;
786786
787787function IORegistryEntryFromPath( masterPort: mach_port_t; const path: io_string_t): io_registry_entry_t; cdecl; external;
788788function IORegistryEntryCreateCFProperty(entry: io_registry_entry_t; key: CFStringRef; allocator:CFAllocatorRef; options: IOOptionBits): CFTypeRef; cdecl; external;
789789function IOObjectRelease(entry: io_registry_entry_t): kern_return_t; cdecl; external;
790790
791791function get_platform_uuid: string;
792792var
793793 ioRegistryRoot :io_registry_entry_t;
794794 uuidCf :CFStringRef;
795795 l :integer;
796796begin
797797 ioRegistryRoot := IORegistryEntryFromPath(kIOMasterPortDefault, 'IOService:/');
798798 uuidCf := CFStringRef (IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0));
799799 IOObjectRelease(ioRegistryRoot);
800800 SetLength(Result, 1024);
801801 CFStringGetCString(uuidCf, @Result[1], length(Result), kCFStringEncodingMacRoman);
802802 CFRelease(uuidCf);
803803 l:=StrLen(@Result[1]);
804804 SetLength(Result, l);
805805end;
806806
807807begin
808808 WriteLn('Platform UUID: ', get_platform_uuid);
809809end.
810810```
811811
812812## External links
813813
814814- [Apple: SysCtl](https://developer.apple.com/documentation/installerjs/system/1812308-sysctl)
815815- [Apple: NSProcessinfo](https://developer.apple.com/documentation/foundation/nsprocessinfo)
816816- [Apple: Gestalt Manager](https://developer.apple.com/documentation/coreservices/carbon_core/gestalt_manager)
817817- [Apple: Introduction to I/O Kit Fundamentals](https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/Introduction/Introduction.html)
818818- [Apple: IOKit framework](https://developer.apple.com/documentation/iokit)
819819- [Apple: DriverKit framework](https://developer.apple.com/documentation/driverkit)
820820- [Apple: Uniquely Identifying a Macintosh Computer](https://developer.apple.com/library/archive/technotes/tn1103/_index.html#//apple_ref/doc/uid/DTS10002943)
821821
822822---
823823*From the Free Pascal wiki, page "Accessing macOS System Information", as it stood on 2022-01-05 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