Как приложение 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?

3 ответа

Решение

Почему старый код сломан?

Код в этом другом ответе хорошо работает для более старых версий Windows. В частности, он обрабатывает вплоть до Windows 8 (версия 6.2) без проблем. Но, как вы заметили, в Windows 8.1 (версия 6.3) и Windows 10 (версия 10.0) дела идут плохо. Код выглядит так, как будто он должен работать, но он получает версию 6.2 для любой версии после Windows 8.

Причина этого заключается в том, что 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. В новом коде вы должны использовать функции помощника по версиям для определения базовой версии Windows. Но есть две проблемы с этими функциями:

  1. Их существует целая куча - одна для обнаружения каждой версии Windows, включая "точечные" версии, и они не экспортируются из какой-либо системной DLL. Скорее, это встроенные функции, определенные в заголовочном файле C/C++, распространяемом с Windows SDK. Это прекрасно работает для программистов на C и C++, но что делать скромному программисту на VB 6? Вы не можете вызвать любую из этих "вспомогательных" функций из VB 6.

  2. Даже если бы вы могли вызывать их из VB 6, Windows 10 расширила охват совместимости (как я уже говорил выше), так что даже IsWindows8Point1OrGreater а также 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, тогда система знает, что вы написали приложение после того, как эта версия была выпущена. Поэтому предполагается, что вы готовы справиться с его критическими изменениями и новыми функциями, поэтому прокладки совместимости не применяются к вашему приложению. Включая, конечно,GetVersionExфункция, которая используется исходным кодом.

Скорее всего, если вы добросовестный разработчик Windows, вы уже встраиваете манифест в свое приложение VB 6. Вам нужен манифест, чтобы получить тематические элементы управления (явно указав версию 6 ComCtl32.dll), чтобы предотвратить виртуализацию UAC (запрашивая толькоasInvoker привилегии) ​​и, возможно, даже для предотвращения виртуализации DPI (пометив себя как осведомленный с высоким DPI). Вы можете найти много информации в Интернете о том, как работают эти и другие параметры в манифестах приложения.

Если вы уже встраиваете файл манифеста в свое приложение, то просто добавить GUID Windows 8.1 и Windows 10 в существующий манифест. Это прорежет ложь OS-версии.

Если вы еще не внедрили файл манифеста, то у вас впереди еще кое-что. VB 6 был выпущен за несколько лет до того, как были задуманы манифесты, и поэтому в IDE нет встроенных средств для их устранения. Вы должны иметь дело с ними самостоятельно. Смотрите здесь советы по встраиванию файла манифеста в VB 6. Короче говоря, это всего лишь текстовые файлы, так что вы можете создать один в Блокноте и вставить его в свой EXE сmt.exe(часть Windows SDK). Существуют различные возможности для автоматизации этого процесса, или вы можете сделать это вручную после завершения сборки.

Альтернативное решение

Если вы не хотите возиться с манифестом, есть другое решение. Он включает только добавление кода в ваш проект 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 запустится и заработает первым. Кроме того, программный запрос WMI из VB 6 затруднен. У нас не так просто, как в PowerShell! Однако, если вы все равно используете WMI, это был бы удобный способ получить читаемую человеком версию ОС. Вы можете сделать это, запросивWin32_OperatingSystem.Name,

Я даже видел другие хаки, такие как чтение версии из блока PEB процесса! Конечно, это для Delphi, а не VB 6, и, поскольку в VB 6 нет встроенной сборки, я даже не уверен, что вы могли бы придумать эквивалент VB 6. Но даже в Delphi это очень плохая идея, поскольку она также опирается на детали реализации. Просто... нет.

В качестве дополнения к вышеуказанному решению для манифеста для GetVersionEx, поместите следующее после блока case 6 для osv.dwVerMajor в коде Коди:

    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: "GetVersionEx может быть изменен или недоступен для выпусков после Windows 8.1". есть что посмотреть, однако.

Чтобы добавить к ответу Коди : помните, что при запуске из IDE VB 6 он сообщит о совместимости, которую вы выбрали для запуска VB 6, например, в Windows 11 из IDE, он сообщает:

Windows XP Home Edition SP2 (V5.1.2600)

Если я компилирую и запускаю исполняемый файл на том же компьютере с Windows 11, он сообщает:

Windows 10 (версия 10.0.2200)

Другие вопросы по тегам