There are many version numbers in Windows, and many different ways to obtain and use them. The plethora of ways to get versioning information, and the many different kinds of components involved, has typically resulted in lots of rope for developers to hang themselves. These messes tend to explode when a new Service Pack comes out or with a new release of the Windows OS. This is often a shame because otherwise the programs work perfectly well on the new version of Windows as long as you lie to them about the OS version number. In fact there's a lot of work put into identifying failing applications and putting in explicit code to lie about the OS version number for each of them, but this manual can't be done for every piece of software on the planet. There's been some recent work to try to automate this, but it too relies on developers to "do the right thing" so ultimately it isn't a problem that can be completely fixed here in Redmond.
The recommendation has long been to not use version numbers. For features that might or might not be available, there is usually a trivial way to handle it that works successfully without ever relying on OS version information. Using LoadLibrary() and GetProcAddress() is perhaps the most well-known way to test for an entry-point that you want to use that might or might not be available. Clever use of /DELAYLOAD can also achieve the same effect, although it's not recommended. Creating a COM factory object and handling failure is also very common. The main point here is that any time you think you need an OS version check in your code: stop and find an alternative. This problem is extremely pervasive, and why we continue to drive this message home through the Games for Windows TR 2.5 advocating the use of the HighVersionLie test in Application Verifier to make sure this problem isn't lurking in your game.
With that said, there is one extremely common and reasonable use for an OS version check: your installer sets a 'minimum bar' of supported OSes. I like to think of them as "your OS must be this high to ride this ride" tests. The critically important aspect of these version checks is that they, when properly written, are unbounded on high-end of the range. Microsoft developers go through a lot of pain and difficulty to ensure that future versions of the OS continue to support the vast majority of existing applications so assuming the worst of a future OS is generally not worth the cost and hassle of blocking 'forward'. It is, however, completely reasonable to set a point below which you don't want to support legacy versions of Windows. This typically makes for cleaner code, and means you don't have to rely as heavily on the LoadLibrary()/GetProcAddress() solution on every Win32 call introduced after Windows 1.0.
So your sold on the idea of having exactly one OS version check in your entire application and it's limited to the installer. Great, what is the right way to do it? Well, as it turns out there are dozens of incorrect ways people have done this ranging from wonky expressions to registry key shenanigans. An extremely robust way to do it is to use the following C++ code example:
HMODULE hMod = LoadLibrary( TEXT("kernel32") );assert( hMod != NULL ); // Win32 programs have to have this module loaded…
OSVERSIONINFOEX osv;memset( &osv, 0, sizeof(osv) );osv.dwOSVersionInfoSize = sizeof(osv);
osv.dwMajorVersion = 5; // Windows XP SP2 or laterosv.dwMinorVersion = 1;osv.wServicePackMajor = 2;osv.wServicePackMinor = 0;
DWORDLONG mask = 0;
typedef ULONGLONG ( WINAPI* fpSetMask )( ULONGLONG, DWORD, BYTE );fpSetMask fpVerSetConditionMask = (fpSetMask)GetProcAddress( hMod, "VerSetConditionMask" );
if (fpVerSetConditionMask != 0) { mask = fpVerSetConditionMask( mask, VER_MAJORVERSION, VER_GREATER_EQUAL ); mask = fpVerSetConditionMask( mask, VER_MINORVERSION, VER_GREATER_EQUAL ); mask = fpVerSetConditionMask( mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL ); mask = fpVerSetConditionMask( mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL );}
typedef BOOL ( WINAPI* fpVerify )( LPOSVERSIONINFOEX, DWORD, DWORDLONG );fpVerify fpVerifyVersionInfo = (fpVerify)GetProcAddress( hMod, "VerifyVersionInfoW" ); // Assumes UNICODE
if ( !fpVerifyVersionInfo || !fpVerifyVersionInfo( &osv, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR, mask ) ) { error("This program requires Windows XP Service Pack 2 or later\n");}
osv.dwMajorVersion = 5; // Windows Server 2003 RTMosv.dwMinorVersion = 2;osv.wServicePackMajor = 0;
mask = 0;
if (fpVerSetConditionMask != 0) { mask = fpVerSetConditionMask( mask, VER_MAJORVERSION, VER_EQUAL ); mask = fpVerSetConditionMask( mask, VER_MINORVERSION, VER_EQUAL ); mask = fpVerSetConditionMask( mask, VER_SERVICEPACKMAJOR, VER_EQUAL );}
assert( fpVerifyVersionInfo != 0 ); // We caught the NULL case already…
if ( fpVerifyVersionInfo( &osv, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, mask ) ) { error("This program requires Windows Server 2003 SP1 or later\n");}
At this point we know the OS already includes the DirectX 9.0c Runtime or later. We know that Direct3D 9, DirectSound8, DirectInput8, etc are all present. We only need to use the DirectX SDK DirectSetup REDIST if our application makes use of optional side-by-side components like D3DX, XAUDIO2, XINPUT, XACT, etc. See DirectX Installation for Game Developers for details on how to configure a minimal package for this purpose.
NOTE: The one thing this doesn't capture is that some seldom used DirectX components were removed from the OS starting with Windows Vista. As long as your application doesn't make use of Direct3D Retained Mode, DirectPlay Voice, or Visual Basic 6.0 DirectX interfaces this should not be an issue.
For many Windows games, it is worth going one step further...
osv.dwMajorVersion = 6; // Windows Vista / Server 2008 RTMosv.dwMinorVersion = 0;osv.wServicePackMajor = 0;
// Reuse mask from last test
if ( fpVerifyVersionInfo( &osv, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, mask ) ) { error(“This program requires Windows Vista SP1, Windows Server 2008 SP1, or later\n");}
At this point we require Windows Vista / Server 2008 SP1 or later if you have Windows Vista or Windows Server 2008. This is useful because it ensures you already have the KB 940105 VA space fix. This also means that the OS includes the DirectX 10.1 runtime.
Windows 8: This check works as designed on Windows 8 for Win32 desktop applications. Windows 8 is officially version "6.2"
Windows 8.1 Preview: This check works as designed on Windows 8.1 for Win32 desktop applications. Windows 8.1 is officially version "6.3". Note that the GetVersion(Ex) API has been actively deprecated on Windows 8.1 and by default will still return "6.2". See this link for more details.
GetVersion(Ex)