Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
530 views
in Technique[技术] by (71.8m points)

winapi - How can a VB 6 app determine if it is running on Windows 10?

I would like my VB 6 application to detect and display the version of Windows that is running on.

I have tried this code from another Stack Overflow question, but it does not work for me. It displays the correct version number on older versions of Windows (like Windows XP and Vista), but it cannot detect Windows 10. For some reason, it says that Windows 10 is Windows 8.

I thought Windows 10 would have a major version of "10" and a minor version of "0", and this chart of Windows version numbers confirms that it does. Why, then, does the GetVersionEx function never actually return version 10.0?

How can I accurately distinguish between Windows 8, Windows 8.1, and Windows 10?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Why is the old code broken?

The code in that other answer works well for older versions of Windows. Specifically, it handles all the way up to Windows 8 (version 6.2) without a hitch. But as you've noticed, things start to go wrong on Windows 8.1 (version 6.3) and Windows 10 (version 10.0). The code looks like it should work, but it's getting version 6.2 for any version after Windows 8.

The reason for this is that Microsoft has decided to change how Windows reports its version number to applications. In an attempt to prevent old programs from erroneously deciding not to run on these latest versions of Windows, the operating system has "peaked out" its version number at 6.2. While Windows 8.1 and 10 still have internal version numbers of 6.3 and 10.0, respectively, they continue to report their version number as 6.2 to older applications. The idea is, essentially, "you cannot handle the truth", so it will be withheld from you. Under the hood, there are compatibility shims between your application and the system that are responsible for faking the version number whenever you call these API functions.

These particular compatibility shims were first introduced in Windows 8.1, and affected several of the version information retrieval APIs. In Windows 10, the compatibility shims begin to affect nearly all of the ways that a version number can be retrieved, including attempts to read the version number directly from system files.

In fact, these old version information retrieval APIs (like the GetVersionEx function used by that other answer) have been officially "deprecated" by Microsoft. In new code, you are supposed to use the Version Helper functions to determine the underlying version of Windows. But there are two problems with these functions:

  1. There are a whole bunch of them—one to detect every version of Windows, including "point" versions—and they are not exported from any system DLL. Rather, they are inline functions defined in a C/C++ header file distributed with the Windows SDK. This works great for C and C++ programmers, but what is a humble VB 6 programmer to do? You can't call any of these "helper" functions from VB 6.

  2. Even if you could call them from VB 6, Windows 10 extended the reach of the compatibility shims (as I mentioned above), so that even the IsWindows8Point1OrGreater and IsWindows10OrGreater functions will lie to you.

A Compatibility Manifest

The ideal solution, and the one that the linked SDK documentation alludes to, is to embed a manifest in your application's EXE with compatibility information. Manifest files were first introduced in Windows XP as a way of bundling metadata with an application, and the amount of information that can be included in a manifest file has increased with each new version of Windows.

The relevant portion of the manifest file is a section called compatibility. It might look something like this (a manifest is just an XML file that adheres to a specific format):

<!-- Declare support for various versions of Windows -->
<ms_compatibility:compatibility xmlns:ms_compatibility="urn:schemas-microsoft-com:compatibility.v1" xmlns="urn:schemas-microsoft-com:compatibility.v1">
  <ms_compatibility:application>
    <!-- Windows Vista/Server 2008 -->
    <ms_compatibility:supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
    <!-- Windows 7/Server 2008 R2 -->
    <ms_compatibility:supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
    <!-- Windows 8/Server 2012 -->
    <ms_compatibility:supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
    <!-- Windows 8.1/Server 2012 R2 -->
    <ms_compatibility:supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
    <!-- Windows 10 -->
    <ms_compatibility:supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
  </ms_compatibility:application>
</ms_compatibility:compatibility>

The way it works is each version of Windows (since Vista) has a GUID, and if your manifest includes that GUID as a supportedOS, then the system knows that you wrote the application after that version was released. It is therefore assumed that you are prepared to deal with its breaking changes and new features, so the compatibility shims are not applied to your application. Including, of course, the GetVersionEx function that is used by the original code.

Chances are, if you are a conscientious Windows developer, you are already embedding a manifest in your VB 6 app. You need a manifest to get themed controls (by explicitly opting-in to version 6 of ComCtl32.dll), to prevent UAC virtualization (by requesting only asInvoker privileges), and perhaps even to prevent DPI virtualization (by marking yourself as high-DPI aware). You can find lots of information online about how these and other settings in application manifests work.

If you are already embedding a manifest file in your app, then it is a simple matter of adding the Windows 8.1 and Windows 10 GUIDs to your existing manifest. This will cut through the OS-version lies.

If you are not already embedding a manifest file, then you have some work ahead of you. VB 6 was released several years before manifests had been conceived, and as such, the IDE does not have any built-in facility to deal with them. You have to deal with them yourself. See here for tips on embedding a manifest file in VB 6. The long and short is that they are just text files, so you can create one in Notepad and embed it into your EXE with mt.exe (part of the Windows SDK). There are various possibilities for automating this process, or you can do it manually after completing a build.

An Alternative Solution

If you don't want to fuss with a manifest, there is another solution. It involves only adding code to your VB 6 project and does not need a manifest of any kind to work.

There is another little-known API function that you can call to retrieve the true OS version. It is actually the internal kernel-mode function that the GetVersionEx and VerifyVersionInfo functions call down to. But when you call it directly, you avoid the compatibility shims that would normally be applied, which means that you get the real, unfiltered version information.

This function is called RtlGetVersion, and as the linked documentation suggests, it is a run-time routine intended for use by drivers. But thanks to the magic of VB 6's ability to dynamically call native API functions, we can use it from our application. The following module shows how it might be used:

'==================================================================================
' RealWinVer.bas     by Cody Gray, 2016
' 
' (Freely available for use and modification, provided that credit is given to the
' original author. Including a comment in the code with my name and/or a link to
' this Stack Overflow answer is sufficient.)
'==================================================================================

Option Explicit

''''''''''''''''''''''''''''''''''''''''''''''''''
' Windows SDK Constants, Types, & Functions
''''''''''''''''''''''''''''''''''''''''''''''''''

Private Const cbCSDVersion As Long = 128 * 2

Private Const STATUS_SUCCESS As Long = 0

Private Const VER_PLATFORM_WIN32s As Long        = 0
Private Const VER_PLATFORM_WIN32_WINDOWS As Long = 1
Private Const VER_PLATFORM_WIN32_NT As Long      = 2

Private Const VER_NT_WORKSTATION As Byte       = 1
Private Const VER_NT_DOMAIN_CONTROLLER As Byte = 2
Private Const VER_NT_SERVER As Byte            = 3

Private Const VER_SUITE_PERSONAL As Integer = &H200

Private Type RTL_OSVERSIONINFOEXW
   dwOSVersionInfoSize As Long
   dwMajorVersion      As Long
   dwMinorVersion      As Long
   dwBuildNumber       As Long
   dwPlatformId        As Long
   szCSDVersion        As String * cbCSDVersion
   wServicePackMajor   As Integer
   wServicePackMinor   As Integer
   wSuiteMask          As Integer
   wProductType        As Byte
   wReserved           As Byte
End Type

Private Declare Function RtlGetVersion Lib "ntdll" _
    (lpVersionInformation As RTL_OSVERSIONINFOEXW) As Long


''''''''''''''''''''''''''''''''''''''''''''''''''
' Internal Helper Functions
''''''''''''''''''''''''''''''''''''''''''''''''''

Private Function IsWinServerVersion(ByRef ver As RTL_OSVERSIONINFOEXW) As Boolean
   ' There are three documented values for "wProductType".
   ' Two of the values mean that the OS is a server versions,
   ' while the other value signifies a home/workstation version.
   Debug.Assert ver.wProductType = VER_NT_WORKSTATION Or _
                ver.wProductType = VER_NT_DOMAIN_CONTROLLER Or _
                ver.wProductType = VER_NT_SERVER

   IsWinServerVersion = (ver.wProductType <> VER_NT_WORKSTATION)
End Function

Private Function GetWinVerNumber(ByRef ver As RTL_OSVERSIONINFOEXW) As String
   Debug.Assert ver.dwPlatformId = VER_PLATFORM_WIN32_NT

   GetWinVerNumber = ver.dwMajorVersion & "." & _
                     ver.dwMinorVersion & "." & _
                     ver.dwBuildNumber
End Function

Private Function GetWinSPVerNumber(ByRef ver As RTL_OSVERSIONINFOEXW) As String
   Debug.Assert ver.dwPlatformId = VER_PLATFORM_WIN32_NT

   If (ver.wServicePackMajor > 0) Then
      If (ver.wServicePackMinor > 0) Then
         GetWinSPVerNumber = "SP" & CStr(ver.wServicePackMajor) & "." & CStr(ver.wServicePackMinor)
         Exit Function
      Else
         GetWinSPVerNumber = "SP" & CStr(ver.wServicePackMajor)
         Exit Function
      End If
   End If
End Function

Private Function GetWinVerName(ByRef ver As RTL_OSVERSIONINFOEXW) As String
   Debug.Assert ver.dwPlatformId = VER_PLATFORM_WIN32_NT

   Select Case ver.dwMajorVersion
      Case 3
         If IsWinServerVersion(ver) Then
            GetWinVerName = "Windows NT 3.5 Server"
            Exit Function
         Else
            GetWinVerName = "Windows NT 3.5 Workstation"
            Exit Function
         End If
      Case 4
         If IsWinServerVersion(ver) Then
            GetWinVerName = "Windows NT 4.0 Server"
            Exit Function
         Else
            GetWinVerName = "Windows NT 4.0 Workstation"
            Exit Function
         End If
      Case 5
         Select Case ver.dwMinorVersion
            Case 0
               If IsWinServerVersio

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...