Gestalt()
是一个纯 C API。接受的答案建议使用NSProcessInfo
但这需要Objective-C代码,如果出于某种原因需要纯C,则不能使用。也是如此NSAppKitVersionNumber
,自 10.13.4 以来 Apple 也不再更新。可以在 C 代码中使用该常量kCFCoreFoundationVersionNumber
,但自 10.11.4 以来此常量不再更新。在 C 中可以读取kern.osrelease
using sysctl
,但需要映射表或依赖当前的内核版本控制方案,因此对于未来版本不可靠,因为内核版本方案可能会更改,并且映射表无法知道未来版本。
Apple 的官方和推荐方法是阅读/System/Library/CoreServices/SystemVersion.plist
,因为这也是Gestalt()
获取版本号的地方,这也是NSProcessInfo()
获取版本号的地方,这也是命令行工具sw_vers
获取版本号的地方,甚至是新@available(macOS 10.x, ...)
的编译器功能从中获取版本号(我深入研究了系统以了解这一点)。#available(macOS 10.x, *)
如果即使 Swift会从那里获得版本号,我也不会感到惊讶。
从那里读取版本号没有简单的 C 调用,所以我编写了以下纯 C 代码,它只需要CoreFoundation
本身是纯 C 框架的框架:
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;
}
使用 adispatch_once_f
而不是的原因dispatch_once
是块也不是纯 C。完全使用的原因dispatch_once
是系统运行时版本号无法更改,因此没有理由在单个应用程序运行期间多次读取版本号。此外,阅读它并不能保证万无一失,而且每次您的应用可能需要它时重新阅读它的成本太高。
谈论不是故障安全,即使它不太可能,阅读它当然可能会失败,在这种情况下,函数会返回false
并且总是会返回false
。我让你在你的应用程序代码中以有意义的方式处理这种情况,因为我不知道知道版本号对你的应用程序有多重要。如果版本号不重要,也许您可以解决它,否则您应该以用户友好的方式使您的应用程序失败。
注意
如果您只需要版本号来执行取决于系统版本的替代代码,则可能有更好的替代方法来查询您自己的版本号。有关详细信息,请参阅此问题。