VB 6 应用程序如何确定它是否在 Windows 10 上运行? [英] How can a VB 6 app determine if it is running on Windows 10?

查看:45
本文介绍了VB 6 应用程序如何确定它是否在 Windows 10 上运行?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我已经尝试过 此代码来自 另一个堆栈溢出问题,但它对我不起作用.它在旧版本的 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?

解决方案

为什么旧代码坏了?

其他答案中的代码适用于旧版本的 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.这个想法本质上是你无法处理真相",所以它会被你隐瞒.在引擎盖下,有 您的应用程序和系统之间的兼容性垫片,每当您调用这些 API 函数时,它们负责伪造版本号.

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

事实上,这些旧版本的信息检索 API(如其他答案使用的 GetVersionEx 函数)已被微软正式弃用".在新代码中,您应该使用 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 文件):

<!-- 声明支持各种版本的 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,那么系统就会知道您编写了应用程序在该版本发布之后.因此,假设您已准备好处理其重大更改和新功能,因此兼容性垫片不会应用于您的应用程序.当然包括原始代码使用的GetVersionEx函数.

如果您是一名尽职尽责的 Windows 开发人员,您很有可能已经在您的 VB 6 应用程序中嵌入了清单.您需要一个清单来获取主题控件(通过明确选择加入 ComCtl32.dll 的第 6 版)、阻止 UAC 虚拟化(通过仅请求 asInvoker 权限),甚至可能阻止 DPI 虚拟化(通过将自己标记为具有高 DPI 意识).您可以在网上找到大量信息,了解应用清单中的这些设置和其他设置的工作原理.

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

如果您还没有嵌入清单文件,那么您还有一些工作要做.VB 6 是在设计清单之前几年发布的,因此,IDE 没有任何内置工具来处理它们.你必须自己处理它们.有关嵌入清单的提示,请参阅此处VB 6 中的文件.总而言之,它们只是文本文件,因此您可以在记事本中创建一个并使用 mt.exe(Windows SDK).自动化此过程有多种可能性,您也可以在完成构建后手动完成.

另一种解决方案

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

您可以调用另一个鲜为人知的 API 函数来检索真实操作系统版本.它实际上是 GetVersionExVerifyVersionInfo 函数调用的内部内核模式函数.但是当你直接调用它时,你避免了通常会应用的兼容性垫片,这意味着你得到了真实的、未经过滤的版本信息.

这个函数被称为RtlGetVersion,正如链接的文档所暗示的那样,它是一个供驱动程序使用的运行时例程.但是,由于 VB 6 具有动态调用本机 API 函数的能力,我们可以在我们的应用程序中使用它.以下模块展示了如何使用它:

'====================================================================================' RealWinVer.bas by Cody Gray, 2016''(可免费使用和修改,前提是已给予' 原作者.在代码中包含我的名字和/或链接的评论' 这个 Stack Overflow 的答案就足够了.)'==================================================================================选项显式''''''''''''''''''''''''''''''''''''''''''''''''' Windows SDK 常量、类型、&职能''''''''''''''''''''''''''''''''''''''''''''''''Private Const cbCSDVersion As Long = 128 * 2私有常量 STATUS_SUCCESS 只要 = 0Private Const VER_PLATFORM_WIN32s As Long = 0私有常量 VER_PLATFORM_WIN32_WINDOWS As Long = 1私有常量 VER_PLATFORM_WIN32_NT As Long = 2Private Const VER_NT_WORKSTATION As Byte = 1Private Const VER_NT_DOMAIN_CONTROLLER As Byte = 2Private Const VER_NT_SERVER As Byte = 3私有常量 VER_SUITE_PERSONAL 作为整数 = &H200私有类型 RTL_OSVERSIONINFOEXWdwOSVersionInfoSize 一样长dwMajorVersion 一样长dwMinorVersion 一样长dwBuildNumber 一样长dwPlatformId 长szCSDVersion 作为字符串 * cbCSDVersionwServicePackMajor 作为整数wServicePackMinor 作为整数wSuiteMask 作为整数wProductType 作为字节w保留为字节结束类型私有声明函数 RtlGetVersion Lib "ntdll" _(lpVersionInformation As RTL_OSVERSIONINFOEXW) 只要''''''''''''''''''''''''''''''''''''''''''''''''' 内部辅助函数''''''''''''''''''''''''''''''''''''''''''''''''私有函数 IsWinServerVersion(ByRef ver As RTL_OSVERSIONINFOEXW) As Boolean' wProductType"有三个记录值.' 其中两个值表示操作系统是服务器版本,' 而另一个值表示家庭/工作站版本.Debug.Assert ver.wProductType = VER_NT_WORKSTATION 或 _ver.wProductType = VER_NT_DOMAIN_CONTROLLER 或 _ver.wProductType = VER_NT_SERVERIsWinServerVersion = (ver.wProductType <> VER_NT_WORKSTATION)结束函数私有函数 GetWinVerNumber(ByRef ver As RTL_OSVERSIONINFOEXW) As StringDebug.Assert ver.dwPlatformId = VER_PLATFORM_WIN32_NTGetWinVerNumber = ver.dwMajorVersion &."&_ver.dwMinorVersion &."&_ver.dwBuildNumber结束函数私有函数 GetWinSPVerNumber(ByRef ver As RTL_OSVERSIONINFOEXW) As StringDebug.Assert ver.dwPlatformId = VER_PLATFORM_WIN32_NT如果 (ver.wServicePackMajor > 0) 那么如果 (ver.wServicePackMinor > 0) 那么GetWinSPVerNumber = "SP" &CStr(ver.wServicePackMajor) &."&CStr(ver.wServicePackMinor)退出函数别的GetWinSPVerNumber = "SP" &CStr(ver.wServicePackMajor)退出函数万一万一结束函数私有函数 GetWinVerName(ByRef ver As RTL_OSVERSIONINFOEXW) As StringDebug.Assert ver.dwPlatformId = VER_PLATFORM_WIN32_NT选择 Case ver.dwMajorVersion案例3If IsWinServerVersion(ver) 然后GetWinVerName = "Windows NT 3.5 服务器"退出函数别的GetWinVerName = "Windows NT 3.5 工作站"退出函数万一案例四If IsWinServerVersion(ver) 然后GetWinVerName = "Windows NT 4.0 服务器"退出函数别的GetWinVerName = "Windows NT 4.0 工作站"退出函数万一案例5选择 Case ver.dwMinorVersion案例 0If IsWinServerVersion(ver) 然后GetWinVerName = "Windows 2000 服务器"退出函数别的GetWinVerName = "Windows 2000 工作站"退出函数万一情况1如果 (ver.wSuiteMask 和 VER_SUITE_PERSONAL) 那么GetWinVerName = "Windows XP 家庭版"退出函数别的GetWinVerName = "Windows XP Professional"退出函数万一案例二If IsWinServerVersion(ver) 然后GetWinVerName = "Windows Server 2003"退出函数别的GetWinVerName = "Windows XP 64 位版本"退出函数万一其他情况Debug.Assert False结束选择案例6选择 Case ver.dwMinorVersion案例 0If IsWinServerVersion(ver) 然后GetWinVerName = "Windows Server 2008"退出函数别的GetWinVerName = "Windows Vista"退出函数万一情况1If IsWinServerVersion(ver) 然后GetWinVerName = "Windows Server 2008 R2"退出函数别的GetWinVerName = "Windows 7"退出函数万一案例二If IsWinServerVersion(ver) 然后GetWinVerName = "Windows Server 2012"退出函数别的GetWinVerName = "Windows 8"退出函数万一案例3If IsWinServerVersion(ver) 然后GetWinVerName = "Windows Server 2012 R2"退出函数别的GetWinVerName = "Windows 8.1"退出函数万一其他情况Debug.Assert False结束选择案例10If IsWinServerVersion(ver) 然后GetWinVerName = "Windows Server 2016"退出函数别的GetWinVerName = "Windows 10"退出函数万一其他情况Debug.Assert False结束选择GetWinVerName = "无法识别的版本"结束函数''''''''''''''''''''''''''''''''''''''''''''''''' 公共职能''''''''''''''''''''''''''''''''''''''''''''''''' 返回一个包含底层 Windows 版本名称的字符串,' 最近安装的服务包的主要版本,以及实际的' 版本号(采用Major.Minor.Build"格式).'' 例如:Windows Server 2003 SP2 (v5.2.3790)"或' "Windows 10 (v10.0.14342)"'' 此函数返回 *real* Windows 版本,并在所有' 操作系统,包括 Windows 10,无论是否' 应用程序包括一个清单.它调用原生的 NT version-info 函数' 直接为了绕过兼容性垫片,否则会撒谎'你关于真正的版本号.公共函数 GetActualWindowsVersion() 作为字符串Dim ver As RTL_OSVERSIONINFOEXWver.dwOSVersionInfoSize = Len(ver)If (RtlGetVersion(ver) <> STATUS_SUCCESS) 然后GetActualWindowsVersion = "无法检索 Windows 版本"万一' 以下版本解析逻辑假设操作系统' 是某些版本的 Windows NT.这个假设将是正确的,如果你' 正在运行过去 15 年中发布的任何版本的 Windows,'包括在此之前发布的几个.Debug.Assert ver.dwPlatformId = VER_PLATFORM_WIN32_NTGetActualWindowsVersion = GetWinVerName(ver) &" " &GetWinSPVerNumber(ver) &_" (v" & GetWinVerNumber(ver) & ")"结束函数

预期的公共接口是一个名为 GetActualWindowsVersion 的函数,它返回一个包含 actual 底层 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 来检索操作系统版本信息.这是一个比 Registry 更好的主意,因为它实际上是一个文档化的公共接口,但它仍然不是一个理想的解决方案.一方面,WMI 是一个非常严重的依赖项.并非所有系统都会运行 WMI,因此您需要确保它已启用,否则您的代码将无法运行.如果这是您唯一需要使用 WMI 的事情,它会非常慢,因为您必须先等待 WMI 启动并运行.此外,从 VB 6 以编程方式查询 WMI 很困难.我们没有那些 PowerShell 人员那么容易!但是,如果您无论如何都在使用 WMI,那么获取人类可读的操作系统版本字符串将是一种方便的方法.您可以通过查询 Win32_OperatingSystem.Name.

我什至见过其他黑客行为,例如从进程的 PEB 块读取版本!当然,这适用于 Delphi,而不是 VB 6,而且由于 VB 6 中没有内联程序集,我什至不确定您是否能想出一个 VB 6 等价物.但即使在 Delphi 中,这也是一个非常糟糕的主意,因为它也依赖于实现细节.只是…不要.

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?

解决方案

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 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

The intended public interface is a single function called GetActualWindowsVersion, which returns a string containing the name of the actual underlying version of Windows. For example, it might return "Windows Server 2003 SP2 (v5.2.3790)" or "Windows 10 (v10.0.14342)".
(Fully tested and working on Windows 10!)

The module's public function calls a couple of internal helper functions that parse information out of the native RTL_OSVERSIONINFOEXW data structure, simplifying the code slightly. There is even more information available in this structure if you want to take the time to modify the code to extract it. For example, there is a wSuiteMask member that contains flags, whose presence indicate certain features or product types. An example of how this information might be used appears in the GetWinVerName helper function, where the VER_SUITE_PERSONAL flag is checked to see if it is Windows XP Home or Pro.

Final Thoughts

There are several other "solutions" to this problem floating around online. I recommend avoiding these.

One popular suggestion is to try and read the version number from the Registry. This is a terrible idea. The Registry is neither intended as nor documented as a public interface for programs. This means such code is relying on implementation details that are subject to change at any time, leaving you back in a situation of breakage—the very problem we are trying to solve in the first place! There is never an advantage in querying the Registry over calling a documented API function.

Another frequently suggested option is to use WMI to retrieve the OS version information. This is a better idea than the Registry, since it is actually a documented, public interface, but it is still not an ideal solution. For one thing, WMI is a very heavy dependency. Not all systems will have WMI running, so you will need to ensure that it is enabled, or your code will not work. And if this is the only thing you need to use WMI for, it will be very slow because you have to wait for WMI to get up and running first. Furthermore, querying WMI programmatically from VB 6 is difficult. We don't have it as easy as those PowerShell folks! However, if you are using WMI anyway, it would be a handy way to get a human-readable OS version string. You can do this by querying Win32_OperatingSystem.Name.

I've even seen other hacks like reading the version from the process's PEB block! Granted, that is for Delphi, not VB 6, and since there is no inline assembly in VB 6, I'm not even sure if you could come up with a VB 6 equivalent. But even in Delphi, this is a very bad idea because it too relies on implementation details. Just…don't.

这篇关于VB 6 应用程序如何确定它是否在 Windows 10 上运行?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆