[英]How can a VB 6 app determine if it is running on Windows 10?
我希望我的 VB 6 应用程序检测并显示正在运行的 Windows 的版本。
我已经从另一个 Stack Overflow 问题中尝试了这段代码,但它对我不起作用。 它在 Windows 的旧版本(如 Windows XP 和 Vista)上显示正确的版本号,但它无法检测到 Windows 10。出于某种原因,它说 Windows 10 是 Windows 8。
我认为 Windows 10 的主要版本为“10”,次要版本为“0”, 这张 Windows 版本号图表证实了这一点。 那么,为什么GetVersionEx
function 实际上从未返回版本 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版本。
原因是微软决定改变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的基础版本。 但这些功能有两个问题:
其中有一大堆 - 一个用于检测每个版本的Windows,包括“点”版本 - 并且它们不会从任何系统DLL导出。 相反,它们是在随Windows SDK一起分发的C / C ++头文件中定义的内联函数。 这对于C和C ++程序员来说非常有用,但是什么是卑微的VB 6程序员呢? 你不能从VB 6调用任何这些“帮助”函数。
即使您可以从VB 6调用它们,Windows 10也扩展了兼容性填充程序的范围(如上所述),因此即使是IsWindows8Point1OrGreater
和IsWindows10OrGreater
函数也会对您IsWindows10OrGreater
。
理想的解决方案以及链接的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>
它的工作方式是每个版本的Windows(因为Vista)都有一个GUID,如果你的清单包含GUID作为supportedOS
的OS,那么系统知道你在该版本发布后编写了应用程序。 因此,假设您已准备好处理其重大更改和新功能,因此兼容性填充程序不会应用于您的应用程序。 当然包括原始代码使用的GetVersionEx
函数。
如果您是一名尽职尽责的Windows开发人员,您可能已经在VB 6应用程序中嵌入了清单。 您需要一个清单来获取主题控件(通过显式选择ComCtl32.dll版本6),以防止UAC虚拟化(通过仅请求asInvoker
权限),甚至可能阻止DPI虚拟化(通过将自己标记为高DPI)知道的)。 您可以在线查找有关应用程序清单中的这些和其他设置如何工作的大量信息 。
如果您已在应用程序中嵌入清单文件,则只需将Windows 8.1和Windows 10 GUID添加到现有清单即可。 这将切入OS版本的谎言。
如果您尚未嵌入清单文件,那么您需要先做一些工作。 VB 6在出现清单之前几年发布,因此,IDE没有任何内置工具来处理它们。 你必须自己处理它们。 有关在VB 6中嵌入清单文件的提示,请参见此处 。 长和短是它们只是文本文件,因此您可以在记事本中创建一个并使用mt.exe
( Windows SDK的一部分)将其嵌入到EXE中。 自动执行此过程有多种可能性,或者您可以在完成构建后手动执行此过程。
如果您不想对清单大惊小怪,还有另一种解决方案。 它只涉及向VB 6项目添加代码,并且不需要任何类型的清单。
还有另一个鲜为人知的API函数,您可以调用它来检索真正的操作系统版本。 它实际上是GetVersionEx
和VerifyVersionInfo
函数调用的内部内核模式函数。 但是当您直接调用它时,可以避免通常应用的兼容性填充程序,这意味着您将获得真实的,未经过滤的版本信息。
此函数称为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上运行!)
该模块的公共函数调用一些内部帮助函数,这些函数解析本机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中,这也是一个非常糟糕的主意,因为它太依赖于实现细节。 这个问题根本。
作为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
来自MSDN的消息:“Windows 8.1之后的版本可能会更改或无法使用GetVersionEx。” 但值得关注的是。
添加到Cody 的回答中:请记住,如果从 VB 6 IDE 运行,它将报告您选择让 VB 6 运行的兼容性,例如,在 IDE 的 Windows 11 上,它报告:
Windows XP 家庭版 SP2 (V5.1.2600)
如果我在同一台 Windows 11 机器上编译并运行可执行文件,它会报告:
Windows 10 (v10.0.2200)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.