Accessing macOS System Information: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: Accessing macOS System Information
| Old | New | Line | |
|---|---|---|---|
| 1 | + | ## Overview | |
| 2 | + | ||
| 3 | + | macOS provides four main ways to access system information: | |
| 4 | + | ||
| 5 | + | - Sysctl interface - An interface used by Unix and Unix-like operating systems to access system hardware and kernel configuration attributes. | |
| 6 | + | - NSProcessInfo class - A class in the Foundation framework which gives access to a collection of information about the current process. | |
| 7 | + | - 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. | |
| 8 | + | - IOKit framework - A framework that implements non-kernel access to I/O Kit objects (drivers and nubs) through the device-interface mechanism. | |
| 9 | + | ||
| 10 | + | These are described below with code examples. | |
| 11 | + | ||
| 12 | + | ## Sysctl | |
| 13 | + | ||
| 14 | + | Sysctl 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). | |
| 15 | + | ||
| 16 | + | The '/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. | |
| 17 | + | ||
| 18 | + | ### FPsysctl() | |
| 19 | + | ||
| 20 | + | > **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. | |
| 21 | + | ||
| 22 | + | The 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: | |
| 23 | + | ||
| 24 | + | ``` pascal | |
| 25 | + | function FPsysctl (Name: pcint; namelen:cuint; oldp:pointer;oldlenp:psize_t; newp:pointer;newlen:size_t):cint; cdecl; external name 'sysctl'; | |
| 26 | + | ``` | |
| 27 | + | ||
| 28 | + | *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. | |
| 29 | + | ||
| 30 | + | *namelen* -- the length of the array of integers in name. | |
| 31 | + | ||
| 32 | + | Note: if the old attribute value is not required, the next two variables can be set to Nil and 0. | |
| 33 | + | ||
| 34 | + | *oldp* -- a pointer to the buffer into which the value is copied. | |
| 35 | + | ||
| 36 | + | *oldlenp* -- the size of the oldp buffer. | |
| 37 | + | ||
| 38 | + | Note: if the new attribute value is not being set, the next two variables should be set to Nil and 0. | |
| 39 | + | ||
| 40 | + | *newp* -- a pointer to the buffer containing the new value to be set. | |
| 41 | + | ||
| 42 | + | *newlen* -- the size of the newp buffer. | |
| 43 | + | ||
| 44 | + | The 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: | |
| 45 | + | ||
| 46 | + | ` --------------------------------------------------------------`\ | |
| 47 | + | ` Name Next level names Description`\ | |
| 48 | + | ` are found in`\ | |
| 49 | + | ` --------------------------------------------------------------`\ | |
| 50 | + | ` CTL_DEBUG sys/sysctl.h Debugging`\ | |
| 51 | + | ` CTL_VFS sys/mount.h File system`\ | |
| 52 | + | ` CTL_HW sys/sysctl.h Generic CPU, I/O`\ | |
| 53 | + | ` CTL_KERN sys/sysctl.h High kernel limits`\ | |
| 54 | + | ` CTL_MACHDEP sys/sysctl.h Machine dependent`\ | |
| 55 | + | ` CTL_NET sys/socket.h Networking`\ | |
| 56 | + | ` CTL_USER sys/sysctl.h User-level`\ | |
| 57 | + | ` CTL_VM sys/resources.h Virtual memory`\ | |
| 58 | + | ` --------------------------------------------------------------` | |
| 59 | + | ||
| 60 | + | Let's look at CTL_HW. The subsequent elements are found in the system sysctl.h file and are: | |
| 61 | + | ||
| 62 | + | ` ------------------------------------------------------------------------------- `\ | |
| 63 | + | ` Name Value Description`\ | |
| 64 | + | ` ------------------------------------------------------------------------------- `\ | |
| 65 | + | ` #define HW_MACHINE 1 /* string: machine class */`\ | |
| 66 | + | ` #define HW_MODEL 2 /* string: specific machine model */`\ | |
| 67 | + | ` #define HW_NCPU 3 /* int: number of cpus */`\ | |
| 68 | + | ` #define HW_BYTEORDER 4 /* int: machine byte order */`\ | |
| 69 | + | ` #define HW_PHYSMEM 5 /* int: total memory */`\ | |
| 70 | + | ` #define HW_USERMEM 6 /* int: non-kernel memory */`\ | |
| 71 | + | ` #define HW_PAGESIZE 7 /* int: software page size */`\ | |
| 72 | + | ` #define HW_DISKNAMES 8 /* strings: disk drive names */`\ | |
| 73 | + | ` #define HW_DISKSTATS 9 /* struct: diskstats[] */`\ | |
| 74 | + | ` #define HW_EPOCH 10 /* int: 0 for Legacy, else NewWorld */`\ | |
| 75 | + | ` #define HW_FLOATINGPT 11 /* int: has HW floating point? */`\ | |
| 76 | + | ` #define HW_MACHINE_ARCH 12 /* string: machine architecture */`\ | |
| 77 | + | ` #define HW_VECTORUNIT 13 /* int: has HW vector unit? */`\ | |
| 78 | + | ` #define HW_BUS_FREQ 14 /* int: Bus Frequency */`\ | |
| 79 | + | ` #define HW_CPU_FREQ 15 /* int: CPU Frequency */`\ | |
| 80 | + | ` #define HW_CACHELINE 16 /* int: Cache Line Size in Bytes */`\ | |
| 81 | + | ` #define HW_L1ICACHESIZE 17 /* int: L1 I Cache Size in Bytes */`\ | |
| 82 | + | ` #define HW_L1DCACHESIZE 18 /* int: L1 D Cache Size in Bytes */`\ | |
| 83 | + | ` #define HW_L2SETTINGS 19 /* int: L2 Cache Settings */`\ | |
| 84 | + | ` #define HW_L2CACHESIZE 20 /* int: L2 Cache Size in Bytes */`\ | |
| 85 | + | ` #define HW_L3SETTINGS 21 /* int: L3 Cache Settings */`\ | |
| 86 | + | ` #define HW_L3CACHESIZE 22 /* int: L3 Cache Size in Bytes */`\ | |
| 87 | + | ` #define HW_TB_FREQ 23 /* int: Bus Frequency */`\ | |
| 88 | + | ` #define HW_MEMSIZE 24 /* uint64_t: physical ram size */`\ | |
| 89 | + | ` #define HW_AVAILCPU 25 /* int: number of available CPUs */`\ | |
| 90 | + | ` #define HW_MAXID 26 /* number of valid hw ids */`\ | |
| 91 | + | ` -------------------------------------------------------------------------------` | |
| 92 | + | ||
| 93 | + | Armed with this information, we can now find out the number of CPUs: | |
| 94 | + | ||
| 95 | + | ``` pascal | |
| 96 | + | // The full source code for this example is available from: | |
| 97 | + | // https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/num_cpu.pas | |
| 98 | + | ... | |
| 99 | + | Uses | |
| 100 | + | SysUtils, SysCtl, Unix; | |
| 101 | + | ... | |
| 102 | + | function NumberOfCPU: Integer; | |
| 103 | + | ||
| 104 | + | var | |
| 105 | + | mib: array[0..1] of Integer; | |
| 106 | + | status : Integer; | |
| 107 | + | len : size_t; | |
| 108 | + | ||
| 109 | + | begin | |
| 110 | + | mib[0] := CTL_HW; | |
| 111 | + | mib[1] := HW_NCPU; | |
| 112 | + | ||
| 113 | + | len := SizeOf(Result); | |
| 114 | + | status := fpSysCtl(PCInt(@mib), Length(mib), @Result, @len, Nil, 0); | |
| 115 | + | if status <> 0 then RaiseLastOSError; | |
| 116 | + | end; | |
| 117 | + | ``` | |
| 118 | + | ||
| 119 | + | The 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). | |
| 120 | + | ||
| 121 | + | There 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'. | |
| 122 | + | ||
| 123 | + | However, before we move on, let's look at retrieving a string with FPsysctl() which is a little different than retrieving an integer. | |
| 124 | + | ||
| 125 | + | ``` pascal | |
| 126 | + | // The full source code for this example is available from: | |
| 127 | + | // https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/hardware_model.pas | |
| 128 | + | ... | |
| 129 | + | Uses | |
| 130 | + | SysUtils, SysCtl, Unix; | |
| 131 | + | ... | |
| 132 | + | function HWmodel : AnsiString; | |
| 133 | + | ||
| 134 | + | var | |
| 135 | + | mib : array[0..1] of Integer; | |
| 136 | + | status : Integer; | |
| 137 | + | len : size_t; | |
| 138 | + | p : PChar; | |
| 139 | + | ||
| 140 | + | begin | |
| 141 | + | mib[0] := CTL_HW; | |
| 142 | + | mib[1] := HW_MODEL; | |
| 143 | + | ||
| 144 | + | status := fpSysCtl(PCInt(@mib), Length(mib), Nil, @len, Nil, 0); | |
| 145 | + | if status <> 0 then RaiseLastOSError; | |
| 146 | + | ||
| 147 | + | GetMem(p, len); | |
| 148 | + | ||
| 149 | + | try | |
| 150 | + | status := fpSysCtl(PCInt(@mib), Length(mib), p, @len, Nil, 0); | |
| 151 | + | if status <> 0 then RaiseLastOSError; | |
| 152 | + | Result := p; | |
| 153 | + | finally | |
| 154 | + | FreeMem(p); | |
| 155 | + | end; | |
| 156 | + | end; | |
| 157 | + | ``` | |
| 158 | + | ||
| 159 | + | Notice 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. | |
| 160 | + | ||
| 161 | + | No, we're not quite ready to move on yet... we still need to see how to retrieve information in a C structure (Pascal record). | |
| 162 | + | ||
| 163 | + | ``` pascal | |
| 164 | + | // The full source code for this example is available from: | |
| 165 | + | // https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/swap_usage.pas | |
| 166 | + | ... | |
| 167 | + | Uses | |
| 168 | + | SysUtils, SysCtl, Unix; | |
| 169 | + | ... | |
| 170 | + | function SwapUsage: String; | |
| 171 | + | ||
| 172 | + | type | |
| 173 | + | swapinfo = record | |
| 174 | + | xsu_total : Int64; | |
| 175 | + | xsu_avail : Int64; | |
| 176 | + | xsu_used : Int64; | |
| 177 | + | xsu_pagesize : Integer; | |
| 178 | + | xsu_encrypted: Boolean; | |
| 179 | + | end; | |
| 180 | + | ||
| 181 | + | const | |
| 182 | + | VM_SWAPUSAGE = 5; | |
| 183 | + | ||
| 184 | + | var | |
| 185 | + | mib : array[0..1] of Integer; | |
| 186 | + | status : Integer; | |
| 187 | + | len : size_t; | |
| 188 | + | swap : swapinfo; | |
| 189 | + | SwapEncrypted: String; | |
| 190 | + | ||
| 191 | + | begin | |
| 192 | + | mib[0] := CTL_VM; | |
| 193 | + | mib[1] := VM_SWAPUSAGE; | |
| 194 | + | swap := Default(swapinfo); | |
| 195 | + | ||
| 196 | + | FillChar(swap, sizeof(swap), 0); | |
| 197 | + | len := sizeof(swap); | |
| 198 | + | ||
| 199 | + | status := fpSysCtl(PCInt(@mib), Length(mib), @swap, @len, Nil, 0); | |
| 200 | + | if status <> 0 then RaiseLastOSError; | |
| 201 | + | ||
| 202 | + | if(swap.xsu_encrypted = true) then | |
| 203 | + | SwapEncrypted := 'Yes' else SwapEncrypted := 'No'; | |
| 204 | + | ||
| 205 | + | Result := 'Swap total: ' + FloatToStr(Round(swap.xsu_total /1024 /1024)) + ' MB' | |
| 206 | + | + LineEnding + 'Swap used: ' + FloatToStr(Round(swap.xsu_used /1024 /1024)) + ' MB' | |
| 207 | + | + LineEnding + 'Swap free: ' + FloatToStr(Round(swap.xsu_avail /1024 /1024)) + ' MB' | |
| 208 | + | + LineEnding + 'Swap page size: ' + IntToStr(swap.xsu_pagesize) + ' bytes' | |
| 209 | + | + LineEnding + 'Swap encrypted: ' + SwapEncrypted + LineEnding; | |
| 210 | + | end; | |
| 211 | + | ``` | |
| 212 | + | ||
| 213 | + | > **Note:** If your computer has not used any swap, then the results for Swap total, used and free will all be zero. | |
| 214 | + | ||
| 215 | + | On my computer this returned: | |
| 216 | + | ||
| 217 | + | ` Swap total: 1024 MB`\ | |
| 218 | + | ` Swap used: 191 MB`\ | |
| 219 | + | ` Swap free: 833 MB`\ | |
| 220 | + | ` Swap page size: 4096 bytes`\ | |
| 221 | + | ` Swap encrypted: Yes` | |
| 222 | + | ||
| 223 | + | The swapinfo record was pretty easily translated from the C structure found in the system `sysctl.h` file: | |
| 224 | + | ||
| 225 | + | ``` c | |
| 226 | + | struct xsw_usage { | |
| 227 | + | u_int64_t xsu_total; | |
| 228 | + | u_int64_t xsu_avail; | |
| 229 | + | u_int64_t xsu_used; | |
| 230 | + | u_int32_t xsu_pagesize; | |
| 231 | + | boolean_t xsu_encrypted; | |
| 232 | + | }; | |
| 233 | + | ``` | |
| 234 | + | ||
| 235 | + | We 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: | |
| 236 | + | ||
| 237 | + | ``` c | |
| 238 | + | #define VM_SWAPUSAGE 5 /* total swap usage */ | |
| 239 | + | ``` | |
| 240 | + | ||
| 241 | + | Finally, yes, it is time to move on to the FPsysctlbyname() function. | |
| 242 | + | ||
| 243 | + | ### FPsysctlbyname | |
| 244 | + | ||
| 245 | + | The FPsysctlbyname function is defined in '/usr/local/share/fpcsrc/rtl/bsd/sysctl.pp' as: | |
| 246 | + | ||
| 247 | + | ``` pascal | |
| 248 | + | function FPsysctlbyname (Name: pchar; oldp:pointer;oldlenp:psize_t; newp:pointer;newlen:size_t):cint; cdecl; external name 'sysctlbyname'; | |
| 249 | + | ``` | |
| 250 | + | ||
| 251 | + | There. 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. | |
| 252 | + | ||
| 253 | + | ``` pascal | |
| 254 | + | // Full source code for this example is available from: | |
| 255 | + | // https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/num_cores.pas | |
| 256 | + | ... | |
| 257 | + | {$mode objfpc} | |
| 258 | + | ... | |
| 259 | + | ||
| 260 | + | Uses | |
| 261 | + | SysCtl, SysUtils, Unix; | |
| 262 | + | ||
| 263 | + | function NumberOfCores: Integer; | |
| 264 | + | ||
| 265 | + | var | |
| 266 | + | status : Integer; | |
| 267 | + | len : size_t; | |
| 268 | + | ||
| 269 | + | begin | |
| 270 | + | len := SizeOf(Result); | |
| 271 | + | ||
| 272 | + | status := fpSysCtlByName('hw.physicalcpu', @Result, @len, Nil, 0); | |
| 273 | + | if status <> 0 then RaiseLastOSError; | |
| 274 | + | end; | |
| 275 | + | ``` | |
| 276 | + | ||
| 277 | + | On 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: | |
| 278 | + | ||
| 279 | + | ` These are the support HW selectors for sysctlbyname. Parameters that are byte counts or frequencies are 64 bit numbers.`\ | |
| 280 | + | ` All other parameters are 32 bit numbers.`\ | |
| 281 | + | ` `\ | |
| 282 | + | ` hw.memsize - The number of bytes of physical memory in the system.`\ | |
| 283 | + | ` hw.ncpu - The maximum number of processors that could be available this boot.`\ | |
| 284 | + | ` Use this value for sizing of static per processor arrays; i.e. processor load statistics.`\ | |
| 285 | + | ` hw.activecpu - The number of processors currently available for executing threads.`\ | |
| 286 | + | ` Use this number to determine the number threads to create in SMP aware applications.`\ | |
| 287 | + | ` This number can change when power management modes are changed.`\ | |
| 288 | + | ` hw.physicalcpu - The number of physical processors available in the current power management mode.`\ | |
| 289 | + | ` hw.physicalcpu_max - The maximum number of physical processors that could be available this boot`\ | |
| 290 | + | ` hw.logicalcpu - The number of logical processors available in the current power management mode.`\ | |
| 291 | + | ` hw.logicalcpu_max - The maximum number of logical processors that could be available this boot`\ | |
| 292 | + | ` hw.tbfrequency - This gives the time base frequency used by the OS and is the basis of all timing services.`\ | |
| 293 | + | ` In general is is better to use mach's or higher level timing services, but this value`\ | |
| 294 | + | ` is needed to convert the PPC Time Base registers to real time.`\ | |
| 295 | + | ` hw.cpufrequency - These values provide the current, min and max cpu frequency. The min and max are for`\ | |
| 296 | + | ` hw.cpufrequency_max - all power management modes. The current frequency is the max frequency in the current mode.`\ | |
| 297 | + | ` hw.cpufrequency_min - All frequencies are in Hz.`\ | |
| 298 | + | ` hw.busfrequency - These values provide the current, min and max bus frequency. The min and max are for`\ | |
| 299 | + | ` hw.busfrequency_max - all power management modes. The current frequency is the max frequency in the current mode.`\ | |
| 300 | + | ` hw.busfrequency_min - All frequencies are in Hz.`\ | |
| 301 | + | ` hw.cputype - These values provide the mach-o cpu type and subtype. A complete list is in <mach/machine.h>`\ | |
| 302 | + | ` hw.cpusubtype - These values should be used to determine what processor family the running cpu is from so that`\ | |
| 303 | + | ` the best binary can be chosen, or the best dynamic code generated. They should not be used`\ | |
| 304 | + | ` to determine if a given processor feature is available.`\ | |
| 305 | + | ` hw.cputhreadtype - This value will be present if the processor supports threads. Like hw.cpusubtype this selector`\ | |
| 306 | + | ` should not be used to infer features, and only used to name the processors thread architecture.`\ | |
| 307 | + | ` The values are defined in <mach/machine.h>`\ | |
| 308 | + | ` hw.byteorder - Gives the byte order of the processor. 4321 for big endian, 1234 for little.`\ | |
| 309 | + | ` hw.pagesize - Gives the size in bytes of the pages used by the processor and VM system.`\ | |
| 310 | + | ` hw.cachelinesize - Gives the size in bytes of the processor's cache lines.`\ | |
| 311 | + | ` This value should be use to control the strides of loops that use cache control instructions`\ | |
| 312 | + | ` like dcbz, dcbt or dcbst.`\ | |
| 313 | + | ` hw.l1dcachesize - These values provide the size in bytes of the L1, L2 and L3 caches. If a cache is not present`\ | |
| 314 | + | ` hw.l1icachesize - then the selector will return an error.`\ | |
| 315 | + | ` hw.l2cachesize -`\ | |
| 316 | + | ` hw.l3cachesize -`\ | |
| 317 | + | ` hw.packages - Gives the number of processor packages.` | |
| 318 | + | ||
| 319 | + | If 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. | |
| 320 | + | ||
| 321 | + | Retrieving 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! | |
| 322 | + | ||
| 323 | + | ``` pascal | |
| 324 | + | // Full source code for this example is available from: | |
| 325 | + | // https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/brand_cpu.pas | |
| 326 | + | ... | |
| 327 | + | {$mode objfpc} | |
| 328 | + | ... | |
| 329 | + | ||
| 330 | + | Uses | |
| 331 | + | SysCtl, SysUtils, Unix; | |
| 332 | + | ||
| 333 | + | function BrandOfCPU: AnsiString; | |
| 334 | + | ||
| 335 | + | var | |
| 336 | + | status : Integer; | |
| 337 | + | len : size_t; | |
| 338 | + | p : PChar; | |
| 339 | + | ||
| 340 | + | begin | |
| 341 | + | status := fpSysCtlByName('machdep.cpu.brand_string', Nil, @len, Nil, 0); | |
| 342 | + | if status <> 0 then RaiseLastOSError; | |
| 343 | + | ||
| 344 | + | GetMem(p, len); | |
| 345 | + | ||
| 346 | + | try | |
| 347 | + | status := fpSysCtlByName('machdep.cpu.brand_string', p, @len, Nil, 0); | |
| 348 | + | if status <> 0 then RaiseLastOSError; | |
| 349 | + | Result := p; | |
| 350 | + | finally | |
| 351 | + | FreeMem(p); | |
| 352 | + | end; | |
| 353 | + | end; | |
| 354 | + | ``` | |
| 355 | + | ||
| 356 | + | On 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*. | |
| 357 | + | ||
| 358 | + | Oh, 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. | |
| 359 | + | ||
| 360 | + | ``` pascal | |
| 361 | + | // Full source code for this example is available from: | |
| 362 | + | // https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/os_version.pas | |
| 363 | + | ||
| 364 | + | // Note: The kern.osproductversion attribute was added to the Darwin kernel in December 2018 | |
| 365 | + | // https://github.com/apple/darwin-xnu/commit/5bbb823c13f3ab1ab58878f96b35433a29882676 | |
| 366 | + | ||
| 367 | + | Program os_version; | |
| 368 | + | ||
| 369 | + | {$mode objfpc} | |
| 370 | + | ||
| 371 | + | Uses | |
| 372 | + | SysCtl, SysUtils, Unix; | |
| 373 | + | ||
| 374 | + | function OS_version : AnsiString; | |
| 375 | + | ||
| 376 | + | var | |
| 377 | + | status : Integer; | |
| 378 | + | len : size_t; | |
| 379 | + | p : PChar; | |
| 380 | + | ||
| 381 | + | begin | |
| 382 | + | status := fpSysCtlByName('kern.osproductversion', Nil, @len, Nil, 0); | |
| 383 | + | if status <> 0 then RaiseLastOSError; | |
| 384 | + | ||
| 385 | + | GetMem(p, len); | |
| 386 | + | ||
| 387 | + | try | |
| 388 | + | status := fpSysCtlByName('kern.osproductversion', p, @len, Nil, 0); | |
| 389 | + | if status <> 0 then RaiseLastOSError; | |
| 390 | + | Result := p; | |
| 391 | + | finally | |
| 392 | + | FreeMem(p); | |
| 393 | + | end; | |
| 394 | + | end; | |
| 395 | + | ||
| 396 | + | begin | |
| 397 | + | WriteLn(OS_version); | |
| 398 | + | end. | |
| 399 | + | ``` | |
| 400 | + | ||
| 401 | + | The above FPC example program will return the operating system version. On my M1 processor Mac mini this was "11.2.3" (Big Sur). | |
| 402 | + | ||
| 403 | + | ### Last words | |
| 404 | + | ||
| 405 | + | For 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. | |
| 406 | + | ||
| 407 | + | It 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. | |
| 408 | + | ||
| 409 | + | ## NSProcessinfo | |
| 410 | + | ||
| 411 | + | Each 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. | |
| 412 | + | ||
| 413 | + | ### Properties | |
| 414 | + | ||
| 415 | + | | Property | Description | | |
| 416 | + | |----|----| | |
| 417 | + | | 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. | | |
| 418 | + | | arguments | Array of strings with the command-line arguments for the process. | | |
| 419 | + | | environment | The variable names (keys) and their values in the environment from which the process was launched. | | |
| 420 | + | | 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. | | |
| 421 | + | | processIdentifier | The identifier of the process (often called process ID or PID). | | |
| 422 | + | | processName | The name of the process. | | |
| 423 | + | | userName | Returns the account name of the current user. macOS 10.12+ | | |
| 424 | + | | fullUserName | Returns the full name of the current user. macOS 10.12+ | | |
| 425 | + | | hostName | The name of the host computer on which the process is executing. | | |
| 426 | + | | operatingSystemVersionString | A string containing the version of the operating system on which the process is executing. macOS 10.2+ | | |
| 427 | + | | operatingSystemVersion | The version of the operating system on which the process is executing. macOS 10.10+ | | |
| 428 | + | | processorCount | The number of processing cores available on the computer. Equivalent to: `sysctl -n hw.ncpu`. macOS 10.5+ | | |
| 429 | + | | activeProcessorCount | The number of active processing cores available on the computer. Equivalent to: `sysctl -n hw.logicalcpu`. macOS 10.5+ | | |
| 430 | + | | physicalMemory | The amount of physical memory on the computer in bytes. macOS 10.5+ | | |
| 431 | + | | systemUptime | The amount of time in seconds that the system has been awake since the last time it was restarted. macOS 10.6+ | | |
| 432 | + | | thermalState | The current thermal state of the system: Nominal, Fair, Serious or Critical. macOS 10.10.3+ | | |
| 433 | + | | automaticTerminationSupportEnabled | A Boolean value indicating whether the app supports automatic termination. Currently, setting this property to False has no effect. macOS 10.7+ | | |
| 434 | + | | | | | |
| 435 | + | ||
| 436 | + | ### Methods | |
| 437 | + | ||
| 438 | + | | Method | Description | | |
| 439 | + | |----|----| | |
| 440 | + | | 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+ | | |
| 441 | + | ||
| 442 | + | ### Example code | |
| 443 | + | ||
| 444 | + | ``` pascal | |
| 445 | + | unit Unit1; | |
| 446 | + | ||
| 447 | + | {$mode objfpc}{$H+} | |
| 448 | + | {$modeswitch objectivec1} | |
| 449 | + | ||
| 450 | + | interface | |
| 451 | + | ||
| 452 | + | uses | |
| 453 | + | Classes, SysUtils, Forms, Dialogs, StdCtrls, | |
| 454 | + | ctypes, // needed for cint | |
| 455 | + | typinfo, // needed for GetEnumName | |
| 456 | + | CocoaAll; // needed for NS... | |
| 457 | + | ||
| 458 | + | type | |
| 459 | + | // From HERE... | |
| 460 | + | NSProcessInfoThermalState = ( | |
| 461 | + | Nominal, // within the normal range; no corrective action is required. | |
| 462 | + | Fair, // thermal state is slightly elevated and the fan is audible. | |
| 463 | + | Serious, // fan is at maximum speed; performance may be affected; | |
| 464 | + | // applications should reduce use of CPU, GPU, and any I/O. | |
| 465 | + | Critical); // thermal state severely affects performance; need immediate cooling; | |
| 466 | + | // applications should reduce use of CPU, GPU, and any I/O to minimum | |
| 467 | + | // required for user interaction; stop using peripherals such as cameras. | |
| 468 | + | ||
| 469 | + | NSOperatingSystemVersion = record | |
| 470 | + | majorVersion: NSInteger; | |
| 471 | + | minorVersion: NSInteger; | |
| 472 | + | patchVersion: NSInteger; | |
| 473 | + | end; | |
| 474 | + | ||
| 475 | + | NSProcessInfo = objcclass external (NSObject) | |
| 476 | + | private | |
| 477 | + | environment_: NSDictionary; | |
| 478 | + | arguments_: NSArray; | |
| 479 | + | hostName_: NSString; | |
| 480 | + | name: NSString; | |
| 481 | + | automaticTerminationOptOutCounter: NSInteger; | |
| 482 | + | public | |
| 483 | + | class function processInfo: NSProcessInfo; message 'processInfo'; | |
| 484 | + | function environment: NSDictionary; message 'environment'; | |
| 485 | + | function arguments: NSArray; message 'arguments'; | |
| 486 | + | function hostName: NSString; message 'hostName'; | |
| 487 | + | procedure setProcessName(newValue: NSString); message 'setProcessName:'; | |
| 488 | + | function processName: NSString; message 'processName'; | |
| 489 | + | function processIdentifier: cint; message 'processIdentifier'; | |
| 490 | + | function globallyUniqueString: NSString; message 'globallyUniqueString'; | |
| 491 | + | function operatingSystemVersionString: NSString; message 'operatingSystemVersionString'; | |
| 492 | + | function userName: NSString; message 'userName'; | |
| 493 | + | function fullUserName: NSString; message 'fullUserName'; | |
| 494 | + | function operatingSystemVersion: NSOperatingSystemVersion; message 'operatingSystemVersion'; | |
| 495 | + | function processorCount: NSUInteger; message 'processorCount'; | |
| 496 | + | function activeProcessorCount: NSUInteger; message 'activeProcessorCount'; | |
| 497 | + | function physicalMemory: culonglong; message 'physicalMemory'; | |
| 498 | + | function isOperatingSystemAtLeastVersion (os_version: NSOperatingSystemVersion): Boolean; message 'isOperatingSystemAtLeastVersion:'; | |
| 499 | + | function systemUptime: NSTimeInterval; message 'systemUptime'; | |
| 500 | + | function thermalState: NSInteger; message 'thermalState'; | |
| 501 | + | {procedure disableSuddenTermination; message 'disableSuddenTermination'; | |
| 502 | + | procedure enableSuddenTermination; message 'enableSuddenTermination'; | |
| 503 | + | procedure disableAutomaticTermination (reason: NSString); message 'disableAutomaticTermination:'; | |
| 504 | + | procedure enableAutomaticTermination (reason: NSString); message 'enableAutomaticTermination:';} | |
| 505 | + | procedure setAutomaticTerminationSupportEnabled(newValue: Boolean); message 'setAutomaticTerminationSupportEnabled:'; | |
| 506 | + | function automaticTerminationSupportEnabled: Boolean; message 'automaticTerminationSupportEnabled'; | |
| 507 | + | end; | |
| 508 | + | // ... To HERE is ONLY NEEDED if you are using FPC 3.0.4 or earlier. FPC 3.2.0 does not need this section. | |
| 509 | + | ||
| 510 | + | { TForm1 } | |
| 511 | + | ||
| 512 | + | TForm1 = Class(TForm) | |
| 513 | + | Button1: TButton; | |
| 514 | + | Memo1: TMemo; | |
| 515 | + | procedure Button1Click(Sender: TObject); | |
| 516 | + | private | |
| 517 | + | ||
| 518 | + | public | |
| 519 | + | ||
| 520 | + | end; | |
| 521 | + | ||
| 522 | + | var | |
| 523 | + | Form1: TForm1; | |
| 524 | + | ||
| 525 | + | implementation | |
| 526 | + | ||
| 527 | + | {$R *.lfm} | |
| 528 | + | ||
| 529 | + | { TForm1 } | |
| 530 | + | ||
| 531 | + | procedure ProcessInfo; | |
| 532 | + | var | |
| 533 | + | thermalState : String; | |
| 534 | + | myDictionary : NSDictionary; | |
| 535 | + | i : Byte = 0; | |
| 536 | + | minOsVer : NSOperatingSystemVersion; | |
| 537 | + | begin | |
| 538 | + | Form1.Memo1.Lines.Clear; | |
| 539 | + | ||
| 540 | + | Form1.Memo1.Lines.Add('Processor count: ' | |
| 541 | + | + IntToStr(NSProcessInfo.ProcessInfo.processorCount) | |
| 542 | + | + LineEnding + 'Active processors: ' | |
| 543 | + | + IntToStr(NSProcessInfo.ProcessInfo.activeProcessorCount) | |
| 544 | + | + LineEnding + 'Physical memory: ' | |
| 545 | + | + FloatToStr(NSProcessInfo.ProcessInfo.physicalMemory / 1024 / 1024 / 1024) + 'GB' | |
| 546 | + | + LinEending + 'Process name: ' | |
| 547 | + | + NSProcessInfo.ProcessInfo.processName.UTF8String | |
| 548 | + | + LinEending + 'PID: ' | |
| 549 | + | + IntToStr(NSProcessInfo.ProcessInfo.processIdentifier) | |
| 550 | + | + LineEnding + 'Uptime: ' | |
| 551 | + | + FormatFloat('0.00', NSProcessInfo.ProcessInfo.systemUptime / 60 / 60 / 24) + ' days' | |
| 552 | + | + LineEnding + 'Operating system: ' | |
| 553 | + | + NSProcessInfo.ProcessInfo.operatingSystemVersionString.UTF8String | |
| 554 | + | + LineEnding | |
| 555 | + | + 'Major: ' + IntToStr(NSProcessInfo.ProcessInfo.OperatingSystemVersion.majorVersion) | |
| 556 | + | + ' Minor: ' + IntToStr(NSProcessInfo.ProcessInfo.OperatingSystemVersion.minorVersion) | |
| 557 | + | + ' Patch: ' + IntToStr(NSProcessInfo.ProcessInfo.OperatingSystemVersion.patchVersion) | |
| 558 | + | + LineEnding + 'Hostname: ' | |
| 559 | + | + NSProcessInfo.ProcessInfo.hostName.UTF8String | |
| 560 | + | + LineEnding + 'Globally unique string: ' | |
| 561 | + | + NSProcessInfo.ProcessInfo.globallyUniqueString.UTF8String | |
| 562 | + | + LineEnding + 'Username: ' | |
| 563 | + | + NSProcessInfo.ProcessInfo.userName.UTF8String | |
| 564 | + | + LineEnding + 'Full Username: ' | |
| 565 | + | + NSProcessInfo.ProcessInfo.fullUserName.UTF8String | |
| 566 | + | + LineEnding | |
| 567 | + | ); | |
| 568 | + | ||
| 569 | + | myDictionary := NSProcessInfo.ProcessInfo.environment; | |
| 570 | + | for i := 0 to (myDictionary.count - 1) do | |
| 571 | + | begin | |
| 572 | + | Form1.Memo1.Lines.Add('Env Var: ' | |
| 573 | + | + myDictionary.allKeys.objectAtIndex(i).UTF8String | |
| 574 | + | + ' = ' | |
| 575 | + | + myDictionary.allValues.objectAtIndex(i).UTF8String | |
| 576 | + | ); | |
| 577 | + | end; | |
| 578 | + | ||
| 579 | + | ThermalState := GetEnumName(TypeInfo(NSProcessInfoThermalState), Ord(NSProcessInfo.ProcessInfo.thermalState)); | |
| 580 | + | Form1.Memo1.Lines.Add(LineEnding + 'Thermal state: ' + ThermalState | |
| 581 | + | ); | |
| 582 | + | ||
| 583 | + | // Setup minimum OS version | |
| 584 | + | minOsVer.majorVersion:= 10; | |
| 585 | + | minOsVer.minorVersion:= 14; | |
| 586 | + | minOsVer.patchVersion:= 6; | |
| 587 | + | ||
| 588 | + | if(NSProcessInfo.ProcessInfo.isOperatingSystemAtLeastVersion(minOSVer)) then | |
| 589 | + | Form1.Memo1.Lines.Add(LineEnding + 'OS version is at least 10.14.6' + LineEnding) | |
| 590 | + | else | |
| 591 | + | Form1.Memo1.Lines.Add(LineEnding + 'OS version is NOT at least 10.14.6' + LineEnding) | |
| 592 | + | end; | |
| 593 | + | ||
| 594 | + | procedure TForm1.Button1Click(Sender: TObject); | |
| 595 | + | begin | |
| 596 | + | processInfo; | |
| 597 | + | end; | |
| 598 | + | ||
| 599 | + | end. | |
| 600 | + | ``` | |
| 601 | + | ||
| 602 | + | ## Gestalt Manager | |
| 603 | + | ||
| 604 | + | Despite 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). | |
| 605 | + | ||
| 606 | + | So 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. | |
| 607 | + | ||
| 608 | + | ### Example: Retrieving OS version from Mac OS X 10.0+ | |
| 609 | + | ||
| 610 | + | ``` pascal | |
| 611 | + | unit Unit1; | |
| 612 | + | ||
| 613 | + | {$mode objfpc}{$H+} | |
| 614 | + | {$modeswitch objectivec2} | |
| 615 | + | ||
| 616 | + | interface | |
| 617 | + | ||
| 618 | + | uses | |
| 619 | + | Classes, SysUtils, Forms, Dialogs, StdCtrls, ExtCtrls, | |
| 620 | + | MacOSAll, | |
| 621 | + | CocoaAll; | |
| 622 | + | ||
| 623 | + | type | |
| 624 | + | ||
| 625 | + | { TForm1 } | |
| 626 | + | ||
| 627 | + | TForm1 = Class(TForm) | |
| 628 | + | Memo1: TMemo; | |
| 629 | + | Button1: TButton; | |
| 630 | + | procedure Button1Click(Sender: TObject); | |
| 631 | + | private | |
| 632 | + | public | |
| 633 | + | ||
| 634 | + | end; | |
| 635 | + | ||
| 636 | + | var | |
| 637 | + | Form1: TForm1; | |
| 638 | + | counter: Int64; | |
| 639 | + | ||
| 640 | + | implementation | |
| 641 | + | ||
| 642 | + | {$R *.lfm} | |
| 643 | + | ||
| 644 | + | { TForm1 } | |
| 645 | + | ||
| 646 | + | procedure ProcessInfo; | |
| 647 | + | begin | |
| 648 | + | Form1.Memo1.Lines.Clear; | |
| 649 | + | ||
| 650 | + | Form1.Memo1.Lines.Add('macOS version: ' | |
| 651 | + | + IntToStr(NSProcessInfo.ProcessInfo.operatingSystemVersion.majorVersion) | |
| 652 | + | + '.' | |
| 653 | + | + IntToStr(NSProcessInfo.ProcessInfo.operatingSystemVersion.minorVersion) | |
| 654 | + | + '.' | |
| 655 | + | + IntToStr(NSProcessInfo.ProcessInfo.operatingSystemVersion.patchVersion) | |
| 656 | + | ); | |
| 657 | + | end; | |
| 658 | + | ||
| 659 | + | Procedure GestaltInfo; | |
| 660 | + | var | |
| 661 | + | majorVersion: LongInt = 0; | |
| 662 | + | minorVersion: LongInt = 0; | |
| 663 | + | patchVersion: LongInt = 0; | |
| 664 | + | begin | |
| 665 | + | Form1.Memo1.Lines.Clear; | |
| 666 | + | Form1.Memo1.Lines.Add('Trying another way...'); | |
| 667 | + | ||
| 668 | + | Gestalt(gestaltSystemVersionMajor, majorVersion); | |
| 669 | + | Gestalt(gestaltSystemVersionMinor, minorVersion); | |
| 670 | + | Gestalt(gestaltSystemVersionBugFix, patchVersion); | |
| 671 | + | ||
| 672 | + | Form1.Memo1.Lines.Add('macOS version: ' + IntToStr(majorVersion) + '.' + | |
| 673 | + | IntToStr(minorVersion) + '.' + IntToStr(patchVersion)); | |
| 674 | + | end; | |
| 675 | + | ||
| 676 | + | procedure TForm1.Button1Click(Sender: TObject); | |
| 677 | + | var | |
| 678 | + | aSEL : SEL; | |
| 679 | + | begin | |
| 680 | + | // Check whether OS version responds to this selector | |
| 681 | + | aSEL := objcselector('operatingSystemVersion'); | |
| 682 | + | if NSProcessInfo.processInfo.respondsToSelector(aSEL) then | |
| 683 | + | ProcessInfo | |
| 684 | + | // Otherwise fallback to the deprecated Gestalt Manager | |
| 685 | + | else | |
| 686 | + | GestaltInfo; | |
| 687 | + | end; | |
| 688 | + | ||
| 689 | + | end. | |
| 690 | + | ``` | |
| 691 | + | ||
| 692 | + | ## IOKit | |
| 693 | + | ||
| 694 | + | IOKit 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. | |
| 695 | + | ||
| 696 | + | While 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. | |
| 697 | + | ||
| 698 | + | ### Example: Retrieving the platform serial number | |
| 699 | + | ||
| 700 | + | ``` pascal | |
| 701 | + | program project1; | |
| 702 | + | ||
| 703 | + | {$mode objfpc}{$H+} | |
| 704 | + | {$modeswitch cvar} | |
| 705 | + | {$linkframework IOKit} | |
| 706 | + | ||
| 707 | + | uses | |
| 708 | + | ctypes, Classes, MacOSAll; | |
| 709 | + | { you can add units after this }; | |
| 710 | + | ||
| 711 | + | type | |
| 712 | + | kern_return_t = cint; // if you use "integer" then you don't need "ctypes" unit | |
| 713 | + | io_object_t = mach_port_t; | |
| 714 | + | io_registry_entry_t = io_object_t; | |
| 715 | + | ||
| 716 | + | io_string_t = array [0..511] of ansichar; | |
| 717 | + | IOOptionBits = UInt32; | |
| 718 | + | ||
| 719 | + | const | |
| 720 | + | kIOPlatformSerialNumber = 'IOPlatformSerialNumber'; | |
| 721 | + | ||
| 722 | + | var | |
| 723 | + | kIOMasterPortDefault:mach_port_t; cvar; external; | |
| 724 | + | ||
| 725 | + | function IORegistryEntryFromPath( masterPort: mach_port_t; const path: io_string_t): io_registry_entry_t; cdecl; external; | |
| 726 | + | function IORegistryEntryCreateCFProperty(entry: io_registry_entry_t; key: CFStringRef; allocator:CFAllocatorRef; options: IOOptionBits): CFTypeRef; cdecl; external; | |
| 727 | + | function IOObjectRelease(entry: io_registry_entry_t): kern_return_t; cdecl; external; | |
| 728 | + | ||
| 729 | + | function get_platform_serial_num: string; | |
| 730 | + | var | |
| 731 | + | ioRegistryRoot :io_registry_entry_t; | |
| 732 | + | serialnumCf :CFStringRef; | |
| 733 | + | l :integer; | |
| 734 | + | begin | |
| 735 | + | ioRegistryRoot := IORegistryEntryFromPath(kIOMasterPortDefault, 'IOService:/'); | |
| 736 | + | serialnumCf := CFStringRef (IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformSerialNumber), kCFAllocatorDefault, 0)); | |
| 737 | + | IOObjectRelease(ioRegistryRoot); | |
| 738 | + | SetLength(Result, 32); | |
| 739 | + | CFStringGetCString(serialnumCf, @Result[1], length(Result), kCFStringEncodingMacRoman); | |
| 740 | + | CFRelease(serialnumCf); | |
| 741 | + | l:=StrLen(@Result[1]); | |
| 742 | + | SetLength(Result, l); | |
| 743 | + | end; | |
| 744 | + | ||
| 745 | + | begin | |
| 746 | + | WriteLn('Platform serial number: ', get_platform_serial_num); | |
| 747 | + | end. | |
| 748 | + | ``` | |
| 749 | + | ||
| 750 | + | ### Example: Retrieving the platform UUID | |
| 751 | + | ||
| 752 | + | A 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. | |
| 753 | + | ||
| 754 | + | ``` pascal | |
| 755 | + | program project1; | |
| 756 | + | ||
| 757 | + | {$mode objfpc}{$H+} | |
| 758 | + | {$modeswitch cvar} | |
| 759 | + | {$linkframework IOKit} | |
| 760 | + | ||
| 761 | + | uses | |
| 762 | + | ctypes, Classes, MacOSAll | |
| 763 | + | { you can add units after this }; | |
| 764 | + | ||
| 765 | + | type | |
| 766 | + | kern_return_t = cint; // if you use "integer" then you don't need "ctypes" unit | |
| 767 | + | io_object_t = mach_port_t; | |
| 768 | + | io_registry_entry_t = io_object_t; | |
| 769 | + | ||
| 770 | + | io_string_t = array [0..511] of ansichar; | |
| 771 | + | IOOptionBits = UInt32; | |
| 772 | + | ||
| 773 | + | const | |
| 774 | + | kIOPlatformUUIDKey = 'IOPlatformUUID'; | |
| 775 | + | ||
| 776 | + | var | |
| 777 | + | kIOMasterPortDefault:mach_port_t; cvar; external; | |
| 778 | + | ||
| 779 | + | function IORegistryEntryFromPath( masterPort: mach_port_t; const path: io_string_t): io_registry_entry_t; cdecl; external; | |
| 780 | + | function IORegistryEntryCreateCFProperty(entry: io_registry_entry_t; key: CFStringRef; allocator:CFAllocatorRef; options: IOOptionBits): CFTypeRef; cdecl; external; | |
| 781 | + | function IOObjectRelease(entry: io_registry_entry_t): kern_return_t; cdecl; external; | |
| 782 | + | ||
| 783 | + | function get_platform_uuid: string; | |
| 784 | + | var | |
| 785 | + | ioRegistryRoot :io_registry_entry_t; | |
| 786 | + | uuidCf :CFStringRef; | |
| 787 | + | l :integer; | |
| 788 | + | begin | |
| 789 | + | ioRegistryRoot := IORegistryEntryFromPath(kIOMasterPortDefault, 'IOService:/'); | |
| 790 | + | uuidCf := CFStringRef (IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0)); | |
| 791 | + | IOObjectRelease(ioRegistryRoot); | |
| 792 | + | SetLength(Result, 1024); | |
| 793 | + | CFStringGetCString(uuidCf, @Result[1], length(Result), kCFStringEncodingMacRoman); | |
| 794 | + | CFRelease(uuidCf); | |
| 795 | + | l:=StrLen(@Result[1]); | |
| 796 | + | SetLength(Result, l); | |
| 797 | + | end; | |
| 798 | + | ||
| 799 | + | begin | |
| 800 | + | WriteLn('Platform UUID: ', get_platform_uuid); | |
| 801 | + | end. | |
| 802 | + | ``` | |
| 803 | + | ||
| 804 | + | ## External links | |
| 805 | + | ||
| 806 | + | - [Apple: SysCtl](https://developer.apple.com/documentation/installerjs/system/1812308-sysctl) | |
| 807 | + | - [Apple: NSProcessinfo](https://developer.apple.com/documentation/foundation/nsprocessinfo) | |
| 808 | + | - [Apple: Gestalt Manager](https://developer.apple.com/documentation/coreservices/carbon_core/gestalt_manager) | |
| 809 | + | - [Apple: Introduction to I/O Kit Fundamentals](https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/Introduction/Introduction.html) | |
| 810 | + | - [Apple: IOKit framework](https://developer.apple.com/documentation/iokit) | |
| 811 | + | - [Apple: DriverKit framework](https://developer.apple.com/documentation/driverkit) | |
| 812 | + | - [Apple: Uniquely Identifying a Macintosh Computer](https://developer.apple.com/library/archive/technotes/tn1103/_index.html#//apple_ref/doc/uid/DTS10002943) | |
| 813 | + | ||
| 814 | + | --- | |
| 815 | + | *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).* |
