【问题标题】:How can a VB 6 app determine if it is running on Windows 10?VB 6 应用程序如何确定它是否在 Windows 10 上运行?
【发布时间】:2016-11-01 08:04:49
【问题描述】:

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

我从another Stack Overflow question 尝试过this code,但它对我不起作用。它在旧版本的 Windows(如 Windows XP 和 Vista)上显示正确的版本号,但无法检测到 Windows 10。由于某种原因,它说 Windows 10 是 Windows 8。

我认为 Windows 10 的主要版本为“10”,次要版本为“0”,this chart of Windows version numbers 确认确实如此。那么,为什么GetVersionEx 函数实际上永远不会返回版本 10.0?

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

【问题讨论】:

  • 您可能需要澄清标题。它读起来更像是“我怎样才能找到在 W10 上运行的 VB6 进程”
  • 您可能还想添加一个注释,说明这些天使用 VB6 是个坏主意 :)
  • 究竟是什么让它成为一个坏主意,@Jonathan?我个人并没有使用它,也有很多年没有使用它了,但是我的 VB 6 答案引起了惊人的关注,所以它比你想象的更受欢迎。它当然失去了它的光泽和吸引力,但我不认为使用它是一个坏主意。似乎有点夸大其词。
  • @CodyGray XP 仍然很受欢迎,但这并不意味着人们应该使用它。

标签: windows winapi vb6 windows-10


【解决方案1】:

作为上述GetVersionEx 清单解决方案的补充,在Cody 的code 中将osv.dwVerMajor 的case 6 块放在之后

    Case 10 'Note: The following works only with updated manifest
       Select Case osv.dwVerMinor
       Case 0
            GetWindowsVersion = "Windows 10/Server 2016"
        Case Else
        End Select

来自 MSDN 的一句话:“GetVersionEx 可能会被更改或无法用于 Windows 8.1 之后的版本。”不过还是值得一看的。

【讨论】:

    【解决方案2】:

    为什么旧代码坏了?

    that other answer 中的代码适用于旧版本的 Windows。具体来说,它可以顺利处理到 Windows 8(版本 6.2)。但正如您所注意到的,Windows 8.1(版本 6.3)和 Windows 10(版本 10.0)开始出现问题。代码看起来应该可以工作,但它正在为 Windows 8 之后的任何版本获取版本 6.2。

    这样做的原因是 Microsoft 已决定更改 Windows 向应用程序报告其版本号的方式。为了防止旧程序错误地决定不在这些最新版本的 Windows 上运行,操作系统已将其版本号“峰值”为 6.2。虽然 Windows 8.1 和 10 仍分别具有内部版本号 6.3 和 10.0,但它们继续向旧应用程序报告其版本号为 6.2。从本质上讲,这个想法是“你无法处理真相”,所以它会被扣留给你。在后台,您的应用程序和系统之间存在compatibility shims,负责在您调用这些 API 函数时伪造版本号。

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

    事实上,这些旧版本的信息检索 API(如其他答案使用的 GetVersionEx 函数)已被 Microsoft 正式“弃用”。在新代码中,您应该使用 Version Helper functions 来确定 Windows 的底层版本。但是这些函数存在两个问题:

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

    2. 即使您可以从 VB 6 调用它们,Windows 10 也扩展了兼容性垫片的范围(如上所述),因此即使是 IsWindows8Point1OrGreaterIsWindows10OrGreater函数会骗你。

    兼容性清单

    理想解决方案,以及链接的 SDK 文档所暗示的解决方案,是在应用程序的 EXE 中嵌入一个带有兼容性信息的清单。 Manifest files 最初是在 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>
    

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

    如果您是一位尽职尽责的 Windows 开发人员,您很有可能已经在您的 VB 6 应用中嵌入了清单。您需要一个清单来获得主题控制(通过明确选择加入 ComCtl32.dll 的第 6 版),防止 UAC 虚拟化(通过仅请求 asInvoker 权限),甚至可能防止 DPI 虚拟化(通过将自己标记为高-DPI 感知)。您可以找到lots of information online,了解应用程序清单中的这些设置和其他设置如何工作。

    如果您已在应用中嵌入清单文件,则只需将 Windows 8.1 和 Windows 10 GUID 添加到现有清单中即可。这将消除操作系统版本的谎言。

    如果您还没有嵌入清单文件,那么您还有一些工作要做。 VB 6 是在清单被构思出来的几年前发布的,因此,IDE 没有任何内置工具来处理它们。你必须自己处理它们。见here for tips on embedding a manifest file in VB 6。总而言之,它们只是文本文件,因此您可以在记事本中创建一个并使用mt.exeWindows SDK 的一部分)将其嵌入到您的 EXE 中。自动化此过程有多种可能性,或者您可以在完成构建后手动执行。

    另一种解决方案

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

    您可以调用另一个鲜为人知的 API 函数来检索 true 操作系统版本。它实际上是 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
    

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

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

    最后的想法

    对于这个问题还有其他几种“解决方案”在网上流传。我建议避免这些。

    一个流行的建议是尝试从注册表中读取版本号。这是一个可怕的想法。注册表既不打算作为程序的公共接口,也不记录为程序的公共接口。这意味着此类代码依赖于随时可能更改的实现细节,让您回到损坏的情况——这正是我们首先要解决的问题!查询注册表永远不会比调用记录在案的 API 函数更有优势。

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

    我什至见过像reading the version from the process's PEB block 这样的其他黑客攻击!当然,那是针对 Delphi,而不是 VB 6,而且由于 VB 6 中没有内联汇编,我什至不确定您是否能想出一个 VB 6 等价物。但即使在 Delphi 中,这也是一个非常糟糕的主意,因为它也依赖于实现细节。只是……不要。

    【讨论】:

    • MS 建议阅读系统 DLL 的产品版本,例如 kernel32.dll。当您想要操作系统名称的文本表示时,WMI 很有用。而且它不受垫片的影响。
    猜你喜欢
    • 2011-06-17
    • 1970-01-01
    • 2015-10-31
    • 2011-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-09
    • 1970-01-01
    相关资源
    最近更新 更多