我希望我的VB 6应用程序能够检测并显示正在运行的Windows版本。
我曾尝试这个代码从另一个堆栈溢出的问题,但它并没有为我工作。它在较旧版本的Windows(例如Windows XP和Vista)上显示正确的版本号,但无法检测到Windows10。出于某种原因,它表示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
其他答案使用的功能)已被Microsoft正式“弃用”。在新代码中,应该使用Version Helper函数来确定Windows的基础版本。但是这些功能有两个问题:
其中有很多(可检测Windows的每个版本,包括“点”版本),并且它们不会从任何系统DLL中导出。而是它们是随Windows SDK一起分发的C / C ++头文件中定义的内联函数。这对于C和C ++程序员来说非常有用,但是谦虚的VB 6程序员应该做什么呢?您不能从VB 6中调用任何这些“帮助器”功能。
即使您可以从VB 6调用它们,Windows 10也会扩展兼容性垫片的范围(如上所述),因此,甚至IsWindows8Point1OrGreater
andIsWindows10OrGreater
函数也将对您不利。
在理想的解决方案,以及一个链接的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
,则系统会知道您在发布该版本后编写了该应用程序。因此,假定您已准备好应对其重大更改和新功能,因此兼容性垫片不会应用于您的应用程序。当然,包括原始代码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函数来检索真实的OS版本。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的东西,它将非常慢,因为您必须等待WMI首先启动并运行。此外,很难从VB 6以编程方式查询WMI。我们没有像PowerShell的那些人那么容易!但是,无论如何,如果使用WMI,这将是获取人类可读的OS版本字符串的便捷方法。您可以通过查询来做到这一点Win32_OperatingSystem.Name
。
我什至看过其他技巧,例如从流程的PEB块中读取版本!当然,那是给Delphi的,而不是VB 6的,而且由于VB 6中没有内联程序集,我什至不确定是否可以提出与VB 6等效的产品。但是即使在Delphi中,这也是一个非常糟糕的主意,因为它也过于依赖实现细节。只是……不。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句