пятница, 4 сентября 2015 г.

Определение версии Windows 

Узнаем версию операционки из C++ кода 

Юрий «yurembo» Язев 
Независимый игродел

Разрабатывая одно приложение, и тестируя его под разными версиями Windows (7, 8.0, 8.1, 10), оказалось, что под последней, для достижения поставленной цели необходимо выполнение другого кода, нежели в первых трех, но это уже другой вопрос, а сейчас, разберемся, как определить «десятку».

Имеется по меньшей мере 4 способа определения версии операционной системы от Microsoft, которые работали в прошлых выпусках Windows.

1. Способ number one. Использование функций GetVersion и GetVersionEx, служащих специально для цели определения версии операционки, под которой выполняется данный код. Обе из них объявлены, как устаревшие, но первая из них была определена устаревшей давно, а вторая, начиная с Windows 8.1. И, вот, почему. Для начала посмотрим, как она используется.

inline void getWindowsVersion()
{
           OSVERSIONINFO osvi = { 0 };
           osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
           GetVersionEx(&osvi);
           std::cout << osvi.dwMajorVersion << "." << osvi.dwMinorVersion << std::endl;
}


Объявляется структура OSVERSIONINFO, полю dwOSVersionInfoSize присваивается ее размер. Затем, указатель на эту структуру передается функции GetVersionEx, которая в процессе своего выполнения заполняет поля структуры сведениями об операционной системе. Откуда поле dwMajorVersion содержит первое число версии, а dwMinorVersion – второе. Вообще, версия Windows состоит из 3-х чисел, разделенных точками: для семерки она выглядит следующим образом: 6.1.7601. Первое число (содержится в поле dwMajorVersion структуры) – это главный номер, второе (dwMinorVersion) – добавочный, третье – номер билда. Таким образом, мы получаем версию Windows.

Консоль Windows 10

Теперь, посмотрим, почему эта функция помечена, как устаревшая и не рекомендуется для использования в операционных системах Windows версии равной и выше 8.1. А, все потому, что значение полей dwMajorVersion и dwMinorVersion структуры OSVERSIONINFO, возвращаемой функцией GetVersionEx одинаковы, как для Windows 8.0, так и для Windows 8.1, а теперь для Windows 10. То есть, для всех трех операционок, с помощью GetVersionEx мы получаем одну и ту же версию – 6.2.
Если необходимо выполнять функции, помеченные нерекомендуемыми для использования, достаточно в начало файла с исходным кодом вставить строчку: #pragma warning(disable: 4996). 

2. Еще один способ определить версию Windows – через сравнение. Для этого, можно воспользоваться функцией VerifyVersionInfo. Она работает следующим образом.

inline BOOL IsWindows_81_Or_Newer()
{
    OSVERSIONINFOEX osvi = { 0 };
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
    osvi.dwMajorVersion = 6;
    osvi.dwMinorVersion = 3;
    DWORDLONG dwlConditionMask = 0;
    VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL);
    VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_GREATER_EQUAL);
    return VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, dwlConditionMask);
}


Уже с ее помощью возможно задетектить Windows 8.1, однако, забегая вперед, с «десяткой» она не работает.
Внутри этой функции первым делом мы объявляем структуру OSVERSIONINFOEX (расширенная версия используемой нами в прошлом разделе), определяем ее размер, а далее, полям dwMajorVersion и dwMinorVersion присваиваем значения определяемой версии Windows, другими словами, угадываем. Затем, с помощью макросов VER_SET_CONDITION мы подготавливаем значение dwlConditionMask типа DWORDLONG для использования в функции VerifyVersionInfo. Следующим шагом передаем ей заполненную нами структуру OSVERSIONINFOEX, флаги, соответствующие определяемым полям структуры и подготовленное на прошлом шаге значение dwlConditionMask. В результате, функция возвращаем булево значение, соответствуют ли значения заполненных нами полей dwMajorVersion и dwMinorVersion реальному номеру операционной системы, под которой выполняется программа.

3. Следующий набор функций работает по похожему принципу, то есть возвращает булевый ответ на запрос об используемой версии Windows. Это такие функции: IsWindows7OrGreater – определяет «семерку» и выше, IsWindows7SP1OrGreater – определяет «семерку» с первым сервис паком и выше, IsWindows8Point1OrGreater – определяет 8.1 или выше и так далее. Эти функции определены в заголовочном файле VersionHelpers.h. Например, в используемой мною Visual Studio 2013 есть всякие определители, начиная от Windows XP со всеми сервис паками, Вистой и, заканчивая 8.1. Ее то можно таким способом определить, но как быть с «десяткой»? Можно воспользоваться Visual Studio 2015, в ней есть функция IsWindows10OrGreater, но мне не хотелось переходить на VS’15.  Ищем дальше более кроссплатформенное решение вопроса.

4. На самом деле, способов много. Чтобы узнать версию Windows можно проанализировать у системных библиотек экспортируемые функции. И, по наличию/отсутствию определенных выяснить версию системы. Решение далеко не блестящее, так как от версии к версии Windows набор экспортируемых функций может сильно изменяться.

5. Раньше версию операционной системы можно было считать из реестра. Однако, в нынешних версиях Windows это трюк не работает.

6. Можно проанализировать структуру PEB выполняемого процесса. Она инициализируется при старте последнего. Среди содержащихся в ней данных присутствует версия Windows.

7. А можно получить версию Windows из файла ntdll.dll. Для этого напишем следующую функцию:

void getRealWindowsVersion()
{
RTL_OSVERSIONINFOEXW *pk_OsVer = new RTL_OSVERSIONINFOEXW;
typedef LONG(WINAPI* tRtlGetVersion)(RTL_OSVERSIONINFOEXW*);

memset(pk_OsVer, 0, sizeof(RTL_OSVERSIONINFOEXW));
pk_OsVer->dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW);

HMODULE h_NtDll = GetModuleHandleW(L"ntdll.dll");
tRtlGetVersion f_RtlGetVersion = (tRtlGetVersion)GetProcAddress(h_NtDll, "RtlGetVersion");

if (!f_RtlGetVersion)
return; // This will never happen (all processes load ntdll.dll)

LONG Status = f_RtlGetVersion(pk_OsVer);

if (Status == 0)
std::cout << "OS version: Windows " << pk_OsVer->dwMajorVersion << "." << pk_OsVer->dwMinorVersion << std::endl;
else
std::cout << 0;

delete pk_OsVer;
}


В начале создается объект структуры RTL_OSVERSIONINFOEXW, содержащей сведения об операционной системе, в том числе ее версию. Затем, этот объект структуры обнуляется, а поле размера инициализируется. Далее, посредством функции GetModuleHandleW мы получаем хэндл на системный файл ntdll.dll. После этого, для получения заполненной корректными данными структуры RTL_OSVERSIONINFOEXW мы вызываем функцию GetProcAddress, которой передаются хэндл файла и имя функции, которую нужно вызвать из системного файла для получения результата. И, наконец, если объект структуры получен, и он не равен 0, тогда получаем из этого объекта 2 номера, составляющих версию операционки, и выводим их в консоль. В самом конце, удаляем объект структуры, созданный нами в начале.

Определение "десятки"

Это далеко не все возможные способы определить версию Windows, под которой в данный момент выполняется приложение. Их – масса. Однако, я отдаю предпочтение последнему – 7-му варианту из-за его независимости от версии и внешних библиотек, а ntdll.dll – есть ядро Windows NT.

Комментариев нет:

Отправить комментарий