簡體   English   中英

VB 6 應用程序如何確定它是否在 Windows 10 上運行?

[英]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的基礎版本。 但這些功能有兩個問題:

  1. 其中有一大堆 - 一個用於檢測每個版本的Windows,包括“點”版本 - 並且它們不會從任何系統DLL導出。 相反,它們是在隨Windows SDK一起分發的C / C ++頭文件中定義的內聯函數。 這對於C和C ++程序員來說非常有用,但是什么是卑微的VB 6程序員呢? 你不能從VB 6調用任何這些“幫助”函數。

  2. 即使您可以從VB 6調用它們,Windows 10也擴展了兼容性填充程序的范圍(如上所述),因此即使是IsWindows8Point1OrGreaterIsWindows10OrGreater函數也會對您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.exeWindows SDK的一部分)將其嵌入到EXE中。 自動執行此過程有多種可能性,或者您可以在完成構建后手動執行此過程。

另類解決方案

如果您不想對清單大驚小怪,還有另一種解決方案。 它只涉及向VB 6項目添加代碼,並且不需要任何類型的清單。

還有另一個鮮為人知的API函數,您可以調用它來檢索真正的操作系統版本。 它實際上是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上運行!)

該模塊的公共函數調用一些內部幫助函數,這些函數解析本機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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM