« Checking the User's Mac OS X Version

Posted by Submission on August 01, 2002 [Feedback (3) & TrackBack (0)]

by Nick Zitzmann

(email)

Preface: http://docs.info.apple.com/article.html?artnum=106176

Mac OS X 10.2 is coming! Along with the various improvements and new features in the OS are a few new APIs for programmers, a few new frameworks, private frameworks being made public, etc. And if you use them, then your program will not work (or, at least, not work correctly) in older versions of Mac OS X.

So, if you're writing some software that must be used in a version of Mac OS X later than 10.0, or if you want your software to behave in one way under one OS while working a completely different way under another, then you must check the user's OS version. If you can get the user's OS version, then you can lock them out if your program absolutely won't work in an older version of OS X, or you can branch code to behave differently under different OS versions, and so on.

There are at least four ways of doing this. One of them is Cocoa-only; the others will work in Carbon applications as well. This article explains how to do the checking; I'll leave the implementation of what to do with the information up to you. After the check is made, the rest should be pretty easy to do. If you need to keep the information past the check, then you could save it in a floating point value or something that you can easily reference.

So, here are your options...

 

// 1. Check AppKit Version Numbers

This method is for Cocoa software only. It is portable to GNUstep and OpenStep; read on for details.

This is pretty easy to do. To date, every new major release of Mac OS X has come with a new version of the Cocoa AppKit framework, so each version of the framework corresponds to a major release of Mac OS X (10.0.x, 10.1.x, 10.2.x, etc.). Plus, there is no special API to use; the information is placed in a double value defined in NSApplication.h called NSAppKitVersionNumber. In Mac OS X 10.1.x, Apple also added a #define that points to the version of the AppKit that was used in Mac OS X 10.0.x, NSAppKitVersionNumber10_0.

This is an easy way to tell if the user is using a particular major release of the OS, since Apple has yet to distribute a completely new AppKit with a point release of the OS. This method is also portable to GNUstep and OpenStep, but on systems other than Mac OS X, the AppKit version should never be used to infer the OS version; it should only be used if you want to use a feature of the AppKit that was only implemented in a certain version number. Instead, to check for the OS version on other platforms, you should use method #3 below.

So, in your code, all you would need to do is check NSAppKitVersionNumber:

#include <math.h>

if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_0)
{
    // User is using Mac OS X 10.0.x or earlier
}
else if (floor(NSAppKitVersionNumber) <= 620)
{
    // User is using Mac OS X 10.1.x
{
else
{
    // User is using some version of Mac OS X greater than 10.1.x
}

(Note: Mac OS X 10.2 may include a #define called NSAppKitVersionNumber10_1 that is equal to 620. But until then, version 620 is the version that came with all releases of OS X 10.1.x.)

 

// 2. Acquire Through Gestalt

This works in Cocoa, Mach-O Carbon, and CFM Carbon software. Because it uses a special Carbon function, it is not portable to other platforms.

This is the way that Mac programmers have used for years to determine the OS version the user is using. It still works today, even in Cocoa apps. If you are using Cocoa, then you just need to remember to include the Carbon frameworks and headers into your project.

This isn't as easy as method #1 above, but it does give you far better information in return about the user's environment. Basically, the call gives you four hexadecimal digits that contains the user's OS X version number. Although it isn't straightforward, this is probably the best overall way of getting the OS X version number, since you also get the point release, it should not false positive, and it works in every type of OS X software where Carbon can be used. The Gestalt Manager is a feature unique to Carbon, and because of this, Gestalt code can not be ported to other platforms.

What you do here is declare an SInt32 that will contain the OS X version number, then call Gestalt() with the key "gestaltSystemVersion" and a pointer to your SInt32, then look at the contents of the SInt32:

SInt32 MacVersion;

if (Gestalt(gestaltSystemVersion, &MacVersion) == noErr)
{
    if (MacVersion == 0x0602)
    {
        // User is using Mac System 6.0.2
        // (No, neither Cocoa nor Carbon work under System 6.0.2,
        // or any OS release prior to System 8.1.
        // This is just an example of how it is done.)
    }
    else if (MacVersion <= 0x0605)
    {
        // User is using Mac System 6.0.5 or earlier
    }
    else if (MacVersion >= 0x0904)
    {
        // User is using Mac System 9.0.4 or later
    }
    else if (MacVersion == 0x1013)
    {
        // User is using Mac OS X 10.1.3
    }
}
You probably get the idea, right? The first two digits contain the Mac System/OS X number, from 01 (System 1, which shipped with the original Macintosh in 1984) to currently 10 (Mac OS X). The next digit contains the major release number, from 0 to 9. The final digit contains the point release, from 0 to 9.

 

// 3. Use the Sysctl API

This works in Cocoa and Mach-O Carbon software, as well as software that uses the command line. It should be portable to other Unix platforms.

The sysctl API is a part of Mac OS X and is described in the files /usr/include/sys/param.h and /usr/include/sys/sysctl.h. sysctl() is a function that, among other things, can be used to obtain the kernel version number. Every version of Mac OS X, even point releases, come with a new version of the Darwin kernel. If you know the version of the Darwin kernel that came with each new version of OS X, then you can use sysctl() to get a rough estimate of what version of OS X the user is using.

You can do it this way, but I still recommend method 2 above over this. Because Darwin kernel version numbers are not the same as Mac OS X version numbers, you need to know what version of the kernel comes with each version of OS X. Also, there is a chance that this check may false positive in the rare case that the user has installed a newer version of the Darwin kernel that came with each point release. However, if you are developing a kernel-level driver or something else that just can't touch Carbon, then this may be the only way you can determine the OS X version. This is also portable to other types of Unix. The Sysctl API has been in BSD for some time now; it may be available on non-BSD Unix distributions.

The sysctl() function works by taking a state that is defined in a Management Information Base (MIB) that is composed of various numbers defined by some predefined constants. The constants you are interested in here are CTL_KERN and KERN_OSRELEASE. You first call sysctl() just to figure out how much memory you'll need for the pointer to the kernel version number variable, allocate the memory, call it again with your newly allocated variable, and then act on that information:

#include <sys/param.h>
#include <sys/sysctl.h>
#include <string.h>
#include <stdlib.h>

// Definitions:
int mib[2];
size_t len;
char *kernelVersion;

// Get the kernel's version as a string called "kernelVersion":
mib[0] = CTL_KERN;
mib[1] = KERN_OSRELEASE;
sysctl(mib, 2, NULL, &len, NULL, 0);
kernelVersion = malloc(len * sizeof(char));
sysctl(mib, 2, kernelVersion, &len, NULL, 0);

// Now look at kernelVersion and compare it to some known values:
if (strcmp(kernelVersion, "1.3") == 0)
{
    // User is using kernel 1.3, which came with Mac OS X 10.0
}
else if (strcmp(kernelVersion, "1.4") == 0)
{
    // User is using kernel 1.4, which came with Mac OS X 10.1
}
else if (strcmp(kernelVersion, "5.1") == 0)
{
    // User is using kernel 5.1, which came with Mac OS X 10.1.1
}
else if (strcmp(kernelVersion, "5.2") == 0)
{
    // User is using kernel 5.2, which came with Mac OS X 10.1.2
}

// Don't forget to free up the memory when you're done with it.
free(kernelVersion);

 

// 4. Acquire from CoreFoundation

This method is currently private and undocumented as of Mac OS X 10.1.5.

It is also possible to determine the OS X version number from the CoreFoundation APIs. However, as of Mac OS X 10.1.5, the API to do so is private and has not yet been released. This may change in future versions of OS X. If the API is ever released, then it should not break when called on older OS X versions.

 

// So... Which Should You Do?

With four methods available, you'll need to pick the one that best fits what you want to do.

  • Method #1 is very simple, fast, easy to program, and shouldn't false positive often. However, it won't work in non-Cocoa programs, and it doesn't give you any specifics about the user's point release. (Sometimes knowing the point release is helpful, since older point releases have bugs that were fixed in newer point releases, and maybe you're writing an application that requires that bug to be fixed.)
  • Method #2 is overall the best method to use, since it doesn't false positive, you'll know about the user's point release, you don't need to know anything about AppKit or kernel version numbers, and it works in most types of Mac OS X software. The only drawbacks here are it uses a system call that is unique to the Mac and thus not portable, and it can't be used in other situations where Carbon can't be used (ie. programming kernel drivers).
  • Method #3 is useful in places where method #2 won't work, since it is portable and it can be used anywhere except in CFM Carbon apps, but otherwise, I don't see any other reason why you'd want to use it.
  • Method #4 may be helpful, but it's not out yet to the general public.
Well, that's about it. Hopefully this will have answered any questions you might have had about this.


Comments

For shell scripts and other programs that can invoke shell commands, there is also the 'sw_vers' command. The output looks like this:

ProductName: Mac OS X
ProductVersion: 10.2.3
BuildVersion: 6G30

Posted by: Bret Aarden on February 8, 2003 05:10 PM

Instead of using sysctl, I would recommend using the uname C call, which tells you other interesting things about the machines and works on other unixes. See:

man 3 uname

Posted by: Kurt Schwehr on May 11, 2003 01:39 PM

It's generally best not to use these calls to test for the presence of particular features in the system. There are already better ways of doing this, for example:

1. Cocoa's -respondsToSelector and +instancesRespondToSelector methods.

2. Mach-O "weak linking".

3. The Objective-C run-time (if you need to test for and possibly use a Cocoa class that might not be on the system).

That way, you don't tie your feature support to knowledge about version numbers.

Posted by: Alastair Houghton on December 9, 2003 08:06 AM
Post a comment