Как приложение 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. Но есть две проблемы с этими функциями:
Их существует целая куча - одна для обнаружения каждой версии Windows, включая "точечные" версии, и они не экспортируются из какой-либо системной DLL. Скорее, это встроенные функции, определенные в заголовочном файле C/C++, распространяемом с Windows SDK. Это прекрасно работает для программистов на C и C++, но что делать скромному программисту на VB 6? Вы не можете вызвать любую из этих "вспомогательных" функций из VB 6.
Даже если бы вы могли вызывать их из 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)