VB 6应用程序如何确定它是否在Windows 10上运行?

Cod*_*ray 5 windows vb6 winapi windows-10

我希望我的VB 6应用程序能够检测并显示正在运行的Windows版本.

我从另一个Stack Overflow问题尝试了这个代码,但它对我不起作用.它在旧版Windows(如Windows XP和Vista)上显示正确的版本号,但无法检测Windows 10.出于某种原因,它表示Windows 10是Windows 8.

我认为Windows 10将有一个主要版本的"10"和一个次要版本的"0",这个Windows版本号图表证实它确实如此.那么,为什么GetVersionEx函数永远不会实际返回10.0版本?

如何准确区分Windows 8,Windows 8.1和Windows 10?

Cod*_*ray 23

为什么旧代码坏了?

其他答案中的代码适用于旧版本的Windows.具体来说,它可以毫不费力地处理Windows 8(版本6.2).但正如您所注意到的,Windows 8.1(版本6.3)和Windows 10(版本10.0)上的问题开始出现问题.代码看起来应该可以工作,但它在Windows 8之后的任何版本都获得了6.2版本.

原因是微软决定改变Windows将其版本号报告给应用程序的方式.为了防止旧程序错误地决定不在这些最新版本的Windows上运行,操作系统已将其版本号"达到峰值"6.2.虽然Windows 8.1和10仍然分别具有6.3和10.0的内部版本号,但它们继续将旧版应用程序的版本号报告为6.2.基本上,这个想法是"你无法处理真相",所以它将被拒绝.在引擎盖下,应用程序和系统之间存在兼容性垫片,负责在调用这些API函数时伪造版本号.

这些特殊的兼容性填充程序最初是在Windows 8.1中引入的,并且影响了几个版本信息检索API.在Windows 10中,兼容性填充程序开始影响几乎所有可以检索版本号的方式,包括尝试直接从系统文件读取版本号.

事实上,这些旧版本信息检索API(如GetVersionEx其他答案所使用的功能)已被Microsoft正式"弃用".在新代码中,您应该使用Version Helper函数来确定Windows的基础版本.但这些功能有两个问题:

  1. 其中有一大堆 - 一个用于检测每个版本的Windows,包括"点"版本 - 并且它们不会从任何系统DLL导出.相反,它们是在随Windows SDK一起分发的C/C++头文件中定义的内联函数.这对于C和C++程序员来说非常有用,但是什么是卑微的VB 6程序员呢?你不能从VB 6中调用任何这些"帮助"函数.

  2. 即使你可以从VB 6中调用它们,Windows 10扩展了兼容性填充程序的范围(如上所述),因此即使是IsWindows8Point1OrGreaterIsWindows10OrGreater函数也会对你不利.

兼容性清单

理想的解决方案,而链接的SDK文档暗示的一条,就是嵌入清单中应用程序的EXE与兼容性信息.清单文件最初是作为将元数据与应用程序捆绑在一起的方式在Windows XP中引入的,并且每个新版本的Windows都可以增加清单文件中包含的信息量.

清单文件的相关部分是一个名为的部分compatibility.它可能看起来像这样(清单只是一个符合特定格式的XML文件):

<!-- 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>
Run Code Online (Sandbox Code Playgroud)

它的工作方式是每个版本的Windows(因为Vista)都有一个GUID,如果你的清单包含那个GUID supportedOS,那么系统就知道你该版本发布编写了应用程序.因此,假设您已准备好处理其重大更改和新功能,因此兼容性填充程序不会应用于您的应用程序.当然包括原始代码GetVersionEx使用的功能.

如果您是一名尽职尽责的Windows开发人员,您可能已经在VB 6应用程序中嵌入了清单.您需要一个清单来获取主题控件(通过显式选择ComCtl32.dll版本6),以防止UAC虚拟化(通过仅请求asInvoker权限),甚至可能阻止DPI虚拟化(通过标记自己高DPI感知) ).您可以在线查找有关应用程序清单中的这些和其他设置如何工作的大量信息.

如果您已在应用程序中嵌入清单文件,则只需将Windows 8.1和Windows 10 GUID添加到现有清单即可.这将切入OS版本的谎言.

如果您尚未嵌入清单文件,那么您需要先做一些工作.VB 6在出现清单之前几年发布,因此,IDE没有任何内置工具来处理它们.你必须自己处理它们.有关在VB 6中嵌入清单文件的提示,请参见此处.长和短是它们只是文本文件,因此您可以在记事本中创建一个并将其嵌入到EXE中mt.exe(部分Windows SDK).自动执行此过程有多种可能性,或者您可以在完成构建后手动执行此过程.

另类解决方案

如果您不想对清单大惊小怪,还有另一种解决方案.它只涉及向VB 6项目添加代码,并且不需要任何类型的清单.

还有另一个鲜为人知的API函数,您可以调用它来检索真正的操作系统版本.它实际上是GetVersionExVerifyVersionInfo函数调用的内部内核模式函数.但是当您直接调用它时,可以避免通常应用的兼容性填充程序,这意味着您将获得真实的,未经过滤的版本信息.

调用此函数RtlGetVersion,并且如链接文档所示,它是一个供驱动程序使用的运行时例程.但是由于VB 6能够动态调用本机API函数,我们可以从我们的应用程序中使用它.以下模块显示了它的使用方式:

'==================================================================================
' 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 IsWinServerVersion(ver) Then
                  GetWinVerName = "Windows 2000 Server"
                  Exit Function
               Else
                  GetWinVerName = "Windows 2000 Workstation"
                  Exit Function
               End If
            Case 1
               If (ver.wSuiteMask And VER_SUITE_PERSONAL) Then
                  GetWinVerName = "Windows XP Home Edition"
                  Exit Function
               Else
                  GetWinVerName = "Windows XP Professional"
                  Exit Function
               End If
            Case 2
               If IsWinServerVersion(ver) Then
                  GetWinVerName = "Windows Server 2003"
                  Exit Function
               Else
                  GetWinVerName = "Windows XP 64-bit Edition"
                  Exit Function
               End If
            Case Else
               Debug.Assert False
         End Select
      Case 6
         Select Case ver.dwMinorVersion
            Case 0
               If IsWinServerVersion(ver) Then
                  GetWinVerName = "Windows Server 2008"
                  Exit Function
               Else
                  GetWinVerName = "Windows Vista"
                  Exit Function
               End If
            Case 1
               If IsWinServerVersion(ver) Then
                  GetWinVerName = "Windows Server 2008 R2"
                  Exit Function
               Else
                  GetWinVerName = "Windows 7"
                  Exit Function
               End If
            Case 2
               If IsWinServerVersion(ver) Then
                  GetWinVerName = "Windows Server 2012"
                  Exit Function
               Else
                  GetWinVerName = "Windows 8"
                  Exit Function
               End If
            Case 3
               If IsWinServerVersion(ver) Then
                  GetWinVerName = "Windows Server 2012 R2"
                  Exit Function
               Else
                  GetWinVerName = "Windows 8.1"
                  Exit Function
               End If
            Case Else
               Debug.Assert False
         End Select
      Case 10
         If IsWinServerVersion(ver) Then
            GetWinVerName = "Windows Server 2016"
            Exit Function
         Else
            GetWinVerName = "Windows 10"
            Exit Function
         End If
      Case Else
         Debug.Assert False
   End Select

   GetWinVerName = "Unrecognized Version"
End Function


''''''''''''''''''''''''''''''''''''''''''''''''''
' Public Functions
''''''''''''''''''''''''''''''''''''''''''''''''''

' Returns a string that contains the name of the underlying version of Windows,
' the major version of the most recently installed service pack, and the actual
' version number (in "Major.Minor.Build" format).
'
' For example: "Windows Server 2003 SP2 (v5.2.3790)" or
'              "Windows 10 (v10.0.14342)"
'
' This function returns the *real* Windows version, and works correctly on all
' operating systems, including Windows 10, regardless of whether or not the
' application includes a manifest. It calls the native NT version-info function
' directly in order to bypass compatibility shims that would otherwise lie to
' you about the real version number.
Public Function GetActualWindowsVersion() As String
   Dim ver As RTL_OSVERSIONINFOEXW
   ver.dwOSVersionInfoSize = Len(ver)

   If (RtlGetVersion(ver) <> STATUS_SUCCESS) Then
      GetActualWindowsVersion = "Failed to retrieve Windows version"
   End If

   ' The following version-parsing logic assumes that the operating system
   ' is some version of Windows NT. This assumption will be true if you
   ' are running any version of Windows released in the past 15 years,
   ' including several that were released before that.
   Debug.Assert ver.dwPlatformId = VER_PLATFORM_WIN32_NT

   GetActualWindowsVersion = GetWinVerName(ver) & " " & GetWinSPVerNumber(ver) & _
                             " (v" & GetWinVerNumber(ver) & ")"
End Function
Run Code Online (Sandbox Code Playgroud)

预期的公共接口是一个名为的函数GetActualWindowsVersion,它返回一个包含Windows 实际底层版本名称的字符串.例如,它可能返回"Windows Server 2003 SP2(v5.2.3790)""Windows 10(v10.0.14342)".
(经过全面测试并在Windows 10上运行!)

模块的公共函数调用一些内部帮助函数,这些函数将信息从本机RTL_OSVERSIONINFOEXW数据结构中解析出来,从而略微简化了代码.如果您想花时间修改代码以提取它,则此结构中有更多可用信息.例如,有一个wSuiteMask包含标志的成员,其存在表示某些功能或产品类型.可以使用此信息的示例显示在GetWinVerName辅助函数中,其中VER_SUITE_PERSONAL检查标志以查看它是Windows XP Home还是Pro.

最后的想法

这个问题还有其他一些"解决方案"可以解决这个问题.我建议避免这些.

一个流行的建议是尝试从注册表中读取版本号.这是一个糟糕的主意.注册表既不打算也不作为程序的公共接口记录.这意味着此类代码依赖于随时可能发生变化的实施细节,让您重新陷入破损的境地 - 我们首先要解决的问题!通过调用已记录的API函数来查询注册表从不会有任何优势.

另一个经常提出的选择是使用WMI来检索操作系统版本信息.这是一个比注册表更好的想法,因为它实际上是一个文档化的公共接口,但它仍然不是一个理想的解决方案.首先,WMI是一个非常重要的依赖.并非所有系统都会运行WMI,因此您需要确保它已启用,否则您的代码将无法运行.如果这是您需要使用WMI的唯一方法,那么它将非常慢,因为您必须等待WMI首先启动并运行.此外,从VB 6以编程方式查询WMI很困难.我们没有那些PowerShell人员那么容易!但是,如果您正在使用WMI,那么获取人类可读的OS版本字符串将是一种方便的方法.您可以通过查询来完成此操作Win32_OperatingSystem.Name.

我甚至看过其他黑客喜欢从进程的PEB块读取版本!当然,这是针对Delphi而不是VB 6,并且由于VB 6中没有内联汇编,我甚至不确定你是否能想出VB 6等价物.但即使在Delphi中,这也是一个非常糟糕的主意,因为它太依赖于实现细节.这个问题根本.

  • MS 建议阅读系统 DLL 的产品版本,如“kernel32.dll”。当您需要操作系统名称的文本表示时,WMI 很有用。它不受垫片的影响。 (2认同)