WindowsDevCenter.com
oreilly.comSafari Books Online.Conferences.

advertisement


AddThis Social Bookmark Button

Where Am I Running?

by Ron Petrusha
08/01/1999

Basilisk Such a seemingly simple task as determining the version of the operating system or the operating system on which an application is running has been surprisingly difficult for many programmers. In the past, the enormous prevalence of errors in version checking in both DOS and Windows led Microsoft to various forms of DOS and Windows version "spoofing" simply to compensate for the fact that developers didn't quite get it right.* The introduction of Microsoft's 32-bit platforms in many ways has made a confusing situation even more confusing. In this article, we'll review how you determine the platform on which your application is running.

Why do you care about this? Why do you need to know about platform and version checking? For the most part, if you confine yourself to programming with VBA and VB (or MSForm) controls, or if your code simply uses VBA to access some of the many COM objects available, operating system and version typically don't concern you. They are very significant, however, in three cases in particular:

  • Your program relies heavily on direct calls to the Win32 API. Although there is supposed to be (and, for many practical programming problems actually is) a common application programming interface that spans Microsoft's 32-bit platforms, platform differences nevertheless abound. Some API functions are present on one platform and absent in the other, or are present in one version but absent in others. Some API functions behave differently on one platform than on another, or on one version than on another.

  • Your program relies on direct registry access to retrieve information. Although the basic structure of the registries in the 32-bit Windows platforms are largely identical, they aren't quite identical. One of the differences, of course, is the presence of some top-level keys (HKEY_PERFORMANCE_DATA in Windows NT and HKEY_DYN_DATA in Windows 9x) that are present in only one operating system platform. A second is the fact that information may be stored in one key on Windows NT, for instance, and in another key on Windows 9x.

  • Your program looks for files in particular directories, and those directories are different in Windows 9x and Windows NT. A good example is the prevalence of system files in \Windows\System in Windows 9x and in Windows\System32 in Windows NT.

32-bit applications, of course, can only run on 32-bit operating systems. This means, as you're no doubt aware, that your application may be running under Windows 9x or Windows NT. It also means, as you may not be aware, that your application may be running on Windows 3.1 under Win32s (even though that's becoming more and more unlikely as the base of Windows 3.x machines decline).

Traditionally, the GetVersion function is used to retrieve version information. The original implementation of the function in the 16-bit Windows API returns a long (four-byte) integer. Its lower-order word contains the version of Windows: the low-order byte contains the major version number, while the high-order byte contains the minor version number. The high-order word reports the MS-DOS version: its major version is stored in the high-order byte, and its minor version is in the low-order byte. This seems clear enough, but for some reason, many developers had difficulty with it. Two problems were particularly common:

Confusing the major and minor version numbers.
Probably because the bytes used to store the major and minor version numbers are the reverse of one another for DOS and Windows, it was very common to retrieve Windows' minor version number and treat it as the major version, and to retrieve the major version and interpret it as the minor version. When this happened, 3.10 -- the official version number reported by Win32 -- became transmogrified into Windows version 10.3.

Testing for a particular version, rather than a baseline version.
Almost all applications are written to take advantage of a particular API or some feature that is new to the underlying operating system. In these cases, the application has to require that it run on the new version of that operating system, and not on earlier versions. One would think that developers would remember, though, that particularly in the software industry, nothing is immutable, and new versions inevitably give rise to even newer versions. In numerous instances, however, someone forgot this, and checked the version in a way similar to the following code fragment:

If Not (nMajorVersion = 3 And nMinorVersion = 10)
   MsgBox "You'd better think about upgrading!", vbOKOnly, 
_
   "Wrong O/S"
   IsVersionCorrect = False
End If

Except in the rare case that the programmer's intention was really to run only under Windows 3.1, the program should check for version 3.1 or greater; instead, it checks for version 3.1 and refuses to run if it finds anything else.

GetVersion is still present under Win32. However, in large measure to prevent silly mistakes like this, the Win32 API includes a new function, GetVersionEx, that also happens to provide the best way to determine the platform and version of the entire family of Microsoft's 32-bit systems software. Its syntax is:

BOOL GetVersionEx(LPOSVERSIONINFO _
    lpVersionInformation) ;

For Visual Basic programmers, this translates into the following Declare statement that should be stored in a code module:

Public Declare Function GetVersionEx Lib "kernel32" _
    Alias "GetVersionExA" _
    (lpVersionInformation As OSVERSIONINFO) As Boolean

The function returns TRUE if it succeeds and FALSE if it fails. Its single parameter is an OSVERSIONINFO data structure that is defined as shown in Table 1. The only requirement in calling the function is that the OSVERSIONINFO's dwOSVersionInfoSize member be assigned an accurate value beforehand.

Table 1. The OSVERSIONINFO structure

MemberTypeDescription
dwOSVersionInfoSize DWORD The size of the OSVERSIONINFO structure. It should be set to len(OSVERSIONINFO) before the GetVersionEx function is called.
dwMajorVersion DWORD The major version of the operating system
dwMinorVersion DWORD The minor version of the operating system
dwBuildNumber DWORD The low-order word contains the operating system's build number; the high-order word identifies the operating system's major and minor versions.
dwPlatformId DWORD The platform supported by the operating system. It can have any of the following constants:
VER_PLATFORM_WIN32S = 0
VER_PLATFORM_WIN32_WINDOWS = 1
VER_PLATFORM_WIN32_NT = 2
szCSDVersion[128] TCHAR A zero-terminated string containing additional information about the operating system.

For Visual Basic programmers, this data structure can be defined by the following Type statement, which is best included in the same code module as the GetVersionEx declaration:

Type OSVERSIONINFO
    dwOSVersionInfoSize As Long
    dwMajorVersion As Long
    dwMinorVersion As Long
    dwBuildNumber As Long
    dwPlatformId As Long
    szCSDVersion As String * 128
End Type

Note that, along with the user-defined type, you also have to include a definition of the platform constants, as follows:

Public Const VER_PLATFORM_WIN32s = 0
Public Const VER_PLATFORM_WIN32_WINDOWS = 1
Public Const VER_PLATFORM_WIN32_NT = 2

This makes it very easy to use Visual Basic to determine the platform on which your application is running. Note from the Declare statement that the lpVersionInformation data structure is passed by reference to the function, and that its szCSDVersion member is defined as a fixed-length string. The GetVersionEx function can then be used to determine the version, as in the GetPlatform function shown in Listing 1. You can call the function by using a code fragment like that shown in Listing 2.

Listing 1. The VB GetPlatform function: determining the OS

Public Function GetPlatform() As Long

Dim udtVer As OSVERSIONINFO
 
udtVer.dwOSVersionInfoSize = Len(udtVer)
  
If (GetVersionEx(udtVer)) Then
  GetPlatform = udtVer.dwPlatformId
Else
  GetPlatform = 0' returns Win32 as lowest common denominator
End If

End Function

Listing 2. Calling the GetPlatform function

Dim strPlatform As String

Select Case GetPlatform
    Case VER_PLATFORM_WIN32_WINDOWS
        strPlatform = "Win9x"
    Case VER_PLATFORM_WIN32_NT
        strPlatform = "WinNT"
    Case VER_PLATFORM_WIN32s
        strPlatform = "Probably unable to run..."
        ' Possibly raise an error here
End Select

GetVersionEx does have one relatively small limitation as far as determining operating system platform is concerned: it doesn't allow you to distinguish between Windows NT Workstation and Windows NT Server, the two varieties of Windows NT. If you want to do that, you'll have to check the Windows NT registry; one of the string values shown in Table 2 that indicates the type of Windows NT is stored as the default value of HKLM\SYSTEM\CurrentControlSet\Control\ProductOptions

Table 2. Possible values of the ProductOptions subkey

REG_SZ StringDescriptionWindows NT Versions
WINNTWindows NT Workstationall
SERVERNTWindows NT Server3.5+
LANMANNTWindows NT Advanced Server3.1

So if you're certain that your application is running under Windows NT, you can determine whether the operating system is NT Server or NT Workstation by calling the IsWorkStation function, which is shown in Listing 4. (The Win32 API declarations needed for registry access are shown in Listing 3; these all can be stored in a code module.)

Listing 3. Win32 API declarations for registry access

Declare Function RegOpenKey Lib "advapi32.dll" _
    Alias "RegOpenKeyA" _
    (ByVal hKey As Long, ByVal lpctstr As String, _
    phkey As Long) As Long

Declare Function RegQueryValue Lib "advapi32.dll" _
    Alias "RegQueryValueA" _
    (ByVal hKey As Long, ByVal lpSubkey As String, _
    ByVal lpValue As String, pcbData As Long) As Long
 
Declare Function RegCloseKey Lib "advapi32.dll" _
    (ByVal hKey As Long) As Long
  
Public Const HKEY_LOCAL_MACHINE = &H80000002

Public Const ERROR_SUCCESS = 0& 

Listing 4. The IsNTWorkstation function

Public Function IsNTWorkstation()

Dim hKey As Long, lenNTType As Long
Dim lngDataType As Long
Dim strNTType As String

IsNTWorkstation = False

If Not (GetPlatform = VER_PLATFORM_WIN32_NT) Then
   ' Probably raise an error and then
   '   Exit Function
End If
If RegOpenKey(HKEY_LOCAL_MACHINE, _ "System\CurrentControlSet\Control\ProductOptions", _ hKey) = ERROR_SUCCESS Then strNTType = String(32, 0) lenNTType = Len(strNTType) If RegQueryValue(hKey, "", strNTType, lenNTType) _ = ERROR_SUCCESS Then If UCase(Left(strNTType, InStr(1, strNTType, Chr(0)) - 1)) = _ "WINNT" Then IsNTWorkstation = True End If End If RegCloseKey hKey End If End Function

Besides making it easy to determine the platform on which your application is running, GetVersionEx makes determining the operating system's version number very simple: you just have to evaluate the OSVERSIONINFO's dwMajorVersion and dwMinorVersion members. Table 3 indicates the version number returned by calling GetVersionEx under existing 32-bit Windows operating systems. Note that, when reporting the version number of Win32s, GetVersionEx returns the version number of Win32s itself; GetVersion, on the other hand, returns 3.10 as the Windows version.

Table 3. Version numbers of 32-bit operating systems reported by GetVersionEx

Operating SystemMajor VersionMinor Version
Win32s10
 110
 115
 120
 125
 130
Windows 9540
Windows 98410
Windows NT310
 350
 351

If you do need to retrieve the version number to make sure that your application is running on an operating system with the appropriate feature set, it's important, unless there's a pressing reason to do otherwise (like the feature that you need is present in one release but discontinued in later releases), to make sure that the operating system version is greater than or equal to some base version. In the case of an application that's expected to run under any of the Windows 9x operating systems (or the .0 release of any operating system), you don't even have to bother with the minor version:

If osVer.dwMajorVersion >= 4
    //  version is OK

In other cases, like if you require that your program run under Windows NT 3.51 or greater, you do have to look at the minor version:

If (osVer.dwMajorVersion = 3 And osVer.dwMinorVersion >= 51) Or 
_ 
     osVer.dwMajorVersion >= 4 Then
         //  version is OK

*Under DOS, the version table that was activated by including the command DEVICE=SETVER.EXE in Config.Sys listed applications that incorrectly queried the version number. Each entry in the table consisted of the application's filename and the version of DOS it thought it required in order to run. When the application queried the DOS version, it was given this number, rather than the actual DOS version number. Windows 3.1 supported compatibility bits, a variety of hexadecimal bit settings stored in the [Compatibility] section of Win.Ini that instructed Windows to provide special handling so that a Win30 application could run under Win31; the bulk of these settings were added to Win.Ini during installation.

Windows 9x continues to use these compatibility bits defined in the [Compatibility] section of Win.Ini. But it goes a bit further, indicating the pervasiveness of these problems. Win95 includes a special utility, MKCOMPAT.EXE, that allows any user to modify the compatibility bits for a particular application. One of its standard options is to (in the program's own words) "lie about Windows' version number"! Surely it says something about the real world of software development when two major operating systems must provide options to lie about something simple like the operating system version number on an application-by-application basis.