[英]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.我希望我的 VB 6 应用程序检测并显示正在运行的 Windows 的版本。
I have tried this code from another Stack Overflow question , but it does not work for me.我已经从另一个 Stack Overflow 问题中尝试了这段代码,但它对我不起作用。 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.
它在 Windows 的旧版本(如 Windows XP 和 Vista)上显示正确的版本号,但它无法检测到 Windows 10。出于某种原因,它说 Windows 10 是 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.我认为 Windows 10 的主要版本为“10”,次要版本为“0”, 这张 Windows 版本号图表证实了这一点。 Why, then, does the
GetVersionEx
function never actually return version 10.0?那么,为什么
GetVersionEx
function 实际上从未返回版本 10.0?
How can I accurately distinguish between Windows 8, Windows 8.1, and Windows 10?如何准确区分 Windows 8、Windows 8.1 和 Windows 10?
The code in that other answer works well for older versions of Windows. 其他答案中的代码适用于旧版本的Windows。 Specifically, it handles all the way up to Windows 8 (version 6.2) without a hitch.
具体来说,它可以毫不费力地处理Windows 8(版本6.2)。 But as you've noticed, things start to go wrong on Windows 8.1 (version 6.3) and Windows 10 (version 10.0).
但正如您所注意到的,Windows 8.1(版本6.3)和Windows 10(版本10.0)上的问题开始出现问题。 The code looks like it should work, but it's getting version 6.2 for any version after Windows 8.
代码看起来应该可以工作,但它在Windows 8之后的任何版本都获得了6.2版本。
The reason for this is that Microsoft has decided to change how Windows reports its version number to applications. 原因是微软决定改变Windows将其版本号报告给应用程序的方式。 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.
为了防止旧程序错误地决定不在这些最新版本的Windows上运行,操作系统已将其版本号“达到峰值”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.
虽然Windows 8.1和10仍然分别具有6.3和10.0的内部版本号,但它们继续将旧版应用程序的版本号报告为6.2。 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.
在引擎盖下,应用程序和系统之间存在兼容性垫片 ,负责在调用这些API函数时伪造版本号。
These particular compatibility shims were first introduced in Windows 8.1, and affected several of the version information retrieval APIs. 这些特殊的兼容性填充程序最初是在Windows 8.1中引入的,并且影响了几个版本信息检索API。 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.
在Windows 10中,兼容性填充程序开始影响几乎所有可以检索版本号的方式,包括尝试直接从系统文件读取版本号。
In fact, these old version information retrieval APIs (like the GetVersionEx
function used by that other answer) have been officially "deprecated" by Microsoft. 实际上,这些旧版本信息检索API(如其他答案所使用的
GetVersionEx
函数)已被Microsoft正式“弃用”。 In new code, you are supposed to use the Version Helper functions to determine the underlying version of Windows. 在新代码中,您应该使用Version Helper函数来确定Windows的基础版本。 But there are two problems with these functions:
但这些功能有两个问题:
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. 其中有一大堆 - 一个用于检测每个版本的Windows,包括“点”版本 - 并且它们不会从任何系统DLL导出。 Rather, they are inline functions defined in a C/C++ header file distributed with the Windows SDK.
相反,它们是在随Windows SDK一起分发的C / C ++头文件中定义的内联函数。 This works great for C and C++ programmers, but what is a humble VB 6 programmer to do?
这对于C和C ++程序员来说非常有用,但是什么是卑微的VB 6程序员呢? You can't call any of these "helper" functions from VB 6.
你不能从VB 6调用任何这些“帮助”函数。
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. 即使您可以从VB 6调用它们,Windows 10也扩展了兼容性填充程序的范围(如上所述),因此即使是
IsWindows8Point1OrGreater
和IsWindows10OrGreater
函数也会对您IsWindows10OrGreater
。
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. 理想的解决方案以及链接的SDK文档所提到的解决方案是在应用程序的EXE中嵌入清单以及兼容性信息。 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.
清单文件最初是作为将元数据与应用程序捆绑在一起的方式在Windows XP中引入的,并且每个新版本的Windows都可以增加清单文件中包含的信息量。
The relevant portion of the manifest file is a section called compatibility
. 清单文件的相关部分是一个名为
compatibility
的部分。 It might look something like this (a manifest is just an XML file that adheres to a specific format): 它可能看起来像这样(清单只是一个符合特定格式的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>
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. 它的工作方式是每个版本的Windows(因为Vista)都有一个GUID,如果你的清单包含GUID作为
supportedOS
的OS,那么系统知道你在该版本发布后编写了应用程序。 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 . 当然包括原始代码使用的
GetVersionEx
函数。
Chances are, if you are a conscientious Windows developer, you are already embedding a manifest in your VB 6 app. 如果您是一名尽职尽责的Windows开发人员,您可能已经在VB 6应用程序中嵌入了清单。 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). 您需要一个清单来获取主题控件(通过显式选择ComCtl32.dll版本6),以防止UAC虚拟化(通过仅请求
asInvoker
权限),甚至可能阻止DPI虚拟化(通过将自己标记为高DPI)知道的)。 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. 如果您已在应用程序中嵌入清单文件,则只需将Windows 8.1和Windows 10 GUID添加到现有清单即可。 This will cut through the OS-version lies.
这将切入OS版本的谎言。
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.
VB 6在出现清单之前几年发布,因此,IDE没有任何内置工具来处理它们。 You have to deal with them yourself.
你必须自己处理它们。 See here for tips on embedding a manifest file in VB 6 .
有关在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 ). 长和短是它们只是文本文件,因此您可以在记事本中创建一个并使用
mt.exe
( Windows SDK的一部分)将其嵌入到EXE中。 There are various possibilities for automating this process, or you can do it manually after completing a build. 自动执行此过程有多种可能性,或者您可以在完成构建后手动执行此过程。
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.
它只涉及向VB 6项目添加代码,并且不需要任何类型的清单。
There is another little-known API function that you can call to retrieve the true OS version. 还有另一个鲜为人知的API函数,您可以调用它来检索真正的操作系统版本。 It is actually the internal kernel-mode function that the
GetVersionEx
and VerifyVersionInfo
functions call down to. 它实际上是
GetVersionEx
和VerifyVersionInfo
函数调用的内部内核模式函数。 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. 此函数称为
RtlGetVersion
,正如链接文档所示,它是一个供驱动程序使用的运行时例程。 But thanks to the magic of VB 6's ability to dynamically call native API functions, we can use it from our application. 但是由于VB 6能够动态调用本机API函数,我们可以从我们的应用程序中使用它。 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. 预期的公共接口是一个名为
GetActualWindowsVersion
函数,它返回一个包含Windows 实际底层版本名称的字符串。 For example, it might return "Windows Server 2003 SP2 (v5.2.3790)" or "Windows 10 (v10.0.14342)" . 例如,它可能返回“Windows Server 2003 SP2(v5.2.3790)”或“Windows 10(v10.0.14342)” 。
(Fully tested and working on Windows 10!) (经过全面测试并在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. 该模块的公共函数调用一些内部帮助函数,这些函数解析本机
RTL_OSVERSIONINFOEXW
数据结构中的信息 ,从而略微简化了代码。 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. 例如,有一个
wSuiteMask
成员包含标志,其存在表示某些功能或产品类型。 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. 可以使用此信息的示例出现在
GetWinVerName
帮助程序函数中,其中检查VER_SUITE_PERSONAL
标志以查看它是Windows XP Home还是Pro。
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.
通过调用已记录的API函数来查询注册表从不会有任何优势。
Another frequently suggested option is to use WMI to retrieve the OS version information. 另一个经常提出的选择是使用WMI来检索操作系统版本信息。 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.
首先,WMI是一个非常重要的依赖。 Not all systems will have WMI running, so you will need to ensure that it is enabled, or your code will not work.
并非所有系统都会运行WMI,因此您需要确保它已启用,否则您的代码将无法运行。 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.
如果这是您需要使用WMI的唯一方法,那么它将非常慢,因为您必须等待WMI首先启动并运行。 Furthermore, querying WMI programmatically from VB 6 is difficult.
此外,从VB 6以编程方式查询WMI很困难。 We don't have it as easy as those PowerShell folks!
我们没有那些PowerShell人员那么容易! However, if you are using WMI anyway, it would be a handy way to get a human-readable OS version string.
但是,如果您正在使用WMI,那么获取人类可读的OS版本字符串将是一种方便的方法。 You can do this by querying
Win32_OperatingSystem.Name
. 您可以通过查询
Win32_OperatingSystem.Name
来执行此操作。
I've even seen other hacks like reading the version from the process's PEB block ! 我甚至看过其他黑客喜欢从进程的PEB块读取版本 ! 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.
当然,这是针对Delphi而不是VB 6,并且由于VB 6中没有内联汇编,我甚至不确定你是否能想出VB 6等价物。 But even in Delphi, this is a very bad idea because it too relies on implementation details.
但即使在Delphi中,这也是一个非常糟糕的主意,因为它太依赖于实现细节。 Just…don't.
这个问题根本。
As an adjunct to the above manifest solution for GetVersionEx , place the following after the case 6 block for osv.dwVerMajor
in Cody's code : 作为GetVersionEx的上述清单解决方案的附件,在Cody的代码中为
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
The word from MSDN: "GetVersionEx may be altered or unavailable for releases after Windows 8.1." 来自MSDN的消息:“Windows 8.1之后的版本可能会更改或无法使用GetVersionEx。” is something to watch, however.
但值得关注的是。
To add to Cody's answer : remember, if running from the VB 6 IDE, it will report the compatibility you selected to get VB 6 to run, for example, on Windows 11 from the IDE, it reports:添加到Cody 的回答中:请记住,如果从 VB 6 IDE 运行,它将报告您选择让 VB 6 运行的兼容性,例如,在 IDE 的 Windows 11 上,它报告:
Windows XP Home Edition SP2 (V5.1.2600)
Windows XP 家庭版 SP2 (V5.1.2600)
If I compile and run the executable on the same Windows 11 machine, it reports:如果我在同一台 Windows 11 机器上编译并运行可执行文件,它会报告:
Windows 10 (v10.0.2200)
Windows 10 (v10.0.2200)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.