Gestalt()
is a pure C API. The accepted answer suggests using NSProcessInfo
but that requires Objective-C code and cannot be used if pure C is a requirement for some reason. Same holds true for NSAppKitVersionNumber
, which also isn't updated anymore by Apple since 10.13.4. Using the constant kCFCoreFoundationVersionNumber
was possible in C code but this constant hasn't been updated anymore since 10.11.4. Reading kern.osrelease
using sysctl
is possible in C but requires a mapping table or relying on the current kernel versioning scheme and thus is not reliable for future version as the kernel version scheme might change and a mapping table cannot know future versions.
The official and recommend way from Apple is to read /System/Library/CoreServices/SystemVersion.plist
as this is also where Gestalt()
gets the version number from, this is also where NSProcessInfo()
gets the version number from, this is also where the command line tool sw_vers
gets the version number from and this is even where the new @available(macOS 10.x, ...)
compiler feature gets the version number from (I diged deep into the system to find out about this). I wouldn't be surprised if even Swift's #available(macOS 10.x, *)
would get the version number from there.
There is just no simple C call for reading the version number from there, so I wrote the following pure C code which only requires CoreFoundation
framework that itself is pure C framework:
static bool versionOK;
static unsigned versions[3];
static dispatch_once_t onceToken;
static
void initMacOSVersion ( void * unused ) {
// `Gestalt()` actually gets the system version from this file.
// Even `if (@available(macOS 10.x, *))` gets the version from there.
CFURLRef url = CFURLCreateWithFileSystemPath(
NULL, CFSTR("/System/Library/CoreServices/SystemVersion.plist"),
kCFURLPOSIXPathStyle, false);
if (!url) return;
CFReadStreamRef readStr = CFReadStreamCreateWithFile(NULL, url);
CFRelease(url);
if (!readStr) return;
if (!CFReadStreamOpen(readStr)) {
CFRelease(readStr);
return;
}
CFErrorRef outError = NULL;
CFPropertyListRef propList = CFPropertyListCreateWithStream(
NULL, readStr, 0, kCFPropertyListImmutable, NULL, &outError);
CFRelease(readStr);
if (!propList) {
CFShow(outError);
CFRelease(outError);
return;
}
if (CFGetTypeID(propList) != CFDictionaryGetTypeID()) {
CFRelease(propList);
return;
}
CFDictionaryRef dict = propList;
CFTypeRef ver = CFDictionaryGetValue(dict, CFSTR("ProductVersion"));
if (ver) CFRetain(ver);
CFRelease(dict);
if (!ver) return;
if (CFGetTypeID(ver) != CFStringGetTypeID()) {
CFRelease(ver);
return;
}
CFStringRef verStr = ver;
// `1 +` for the terminating NUL (\0) character
CFIndex size = 1 + CFStringGetMaximumSizeForEncoding(
CFStringGetLength(verStr), kCFStringEncodingASCII);
// `calloc` initializes the memory with all zero (all \0)
char * cstr = calloc(1, size);
if (!cstr) {
CFRelease(verStr);
return;
}
CFStringGetBytes(ver, CFRangeMake(0, CFStringGetLength(verStr)),
kCFStringEncodingASCII, '?', false, (UInt8 *)cstr, size, NULL);
CFRelease(verStr);
printf("%s\n", cstr);
int scans = sscanf(cstr, "%u.%u.%u",
&versions[0], &versions[1], &versions[2]);
free(cstr);
// There may only be two values, but only one is definitely wrong.
// As `version` is `static`, its zero initialized.
versionOK = (scans >= 2);
}
static
bool macOSVersion (
unsigned *_Nullable outMajor,
unsigned *_Nullable outMinor,
unsigned *_Nullable outBugfix
) {
dispatch_once_f(&onceToken, NULL, &initMacOSVersion);
if (versionOK) {
if (outMajor) *outMajor = versions[0];
if (outMinor) *outMinor = versions[1];
if (outBugfix) *outBugfix = versions[2];
}
return versionOK;
}
The reason for using a dispatch_once_f
instead of dispatch_once
is that blocks aren't pure C either. The reason for using dispatch_once
at all is that the version number cannot change while the system is running, so there is no reason for reading the version number more than once during a single app run. Also reading it is not guaranteed to be fail-safe and too expensive to re-read it every time your app may require it.
Talking about not being fail-safe, even though it is unlikely, reading it may, of course, fail, in which case the function returns false
and always will return false
. I leave it up to you to handle that case in a meaningful way in your app code because I cannot know how critical knowing the version number is to your app. If the version number is not critical, maybe you can work around it, otherwise you should make your app fail in a user friendly way.
Note
If you only require the version number to execute alternative code depending on the system version, there might be a better alternative to querying the version number on your own. See this question for details.