:: MVP ::
|
|
:: RSS ::
|
|
|
Сегодня хотелось бы поговорить о граблях, о которые набило шишку огромное (судя по интернет форумам) количество
разработчиков. Имя им – GetVersion
и GetVersionEx.
Я был сильно удивлен, увидев, что выдают эти функции в Windows 8.1. Так, на планшете под управлением Windows 8.1 x86,
вместо ожидаемого значения 6.3
я увидел значение 6.1,
а на виртуальной машине под управлением Windows 8.1 x64 функция “порадовала” значением
6.2.
Объяснение такому поведению нашлось довольно быстро, в описании этих функций в MSDN. Вот что там написано:
With the release of Windows 8.1, the behavior of the GetVersion(Ex) API has changed in the value it will return for the
operating system version. The value returned by the GetVersion(Ex) function now depends on how the application is manifested.
Applications not manifested for Windows 8.1 or Windows 10 will return the Windows 8 OS version value (6.2). Once an application is
manifested for a given operating system version, GetVersion(Ex) will always return the version that the application is manifested
for in future releases. To manifest your applications for Windows 8.1 or Windows 10, refer to
Targeting your application for Windows.
В вольном переводе это означает то, что разработчик должен задекларировать совместимость приложения с операционными системами
семейства Windows от версии 8 и выше (об этом хорошо написано в блоге
GunSmoker-а).
К сожалению, мои попытки подружить приложение с манифестом и заставить функции
GetVersion и
GetVersionEx выдавать
правильный результат не увенчались успехом. Ниже привожу содержание файла манифеста, с которым я эксперементировал.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<dependency>
<dependentAssembly>
<assemblyIdentity
version="6.0.0.0"
publicKeyToken="6595b64144ccf1df"
name="Microsoft.Windows.Common-Controls"
type="win32"
processorArchitecture="*"
/>
</dependentAssembly>
</dependency>
<description>Здесь - описание вашего приложения.</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- This Id value indicates the application supports
Windows Vista and Windows Server 2008 functionality -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<!-- This Id value indicates the application supports
Windows 7 and Windows Server 2008 R2 functionality -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!-- This Id value indicates the application supports
Windows 8 and Windows Server 2012 functionality -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- This Id value indicates the application supports
Windows 8.1 and Windows Server 2012 R2 functionality -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- This Id value indicates the application supports
Windows 10 functionality -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>
|
Было бы неплохо, если бы в настройках проекта появились соответствующие опциональные настройки для автоматической генерации
корректного манифеста (имеющихся настроек явно недостаточно). Надеюсь когда-нибудь разработчики это сделают.
Тем не менее, этот подход является работоспособным, подтверждение чему я нашел и интернете и прикладываю в
конце статьи (GetVersionEx.zip).
Также в MSDN есть ссылка на
Version Helper functions, но поскольку корректность работы некоторых функций также зависит от манифеста (например
IsWindows8Point1OrGreater
и IsWindows10OrGreater),
я не стал экспериментировать с ними.
В поисках ответа на вопрос, каким же еще образом можно определить точную версию Windows, удалось найти еще несколько решений.
Первое решение, которое у меня не вызывает особого доверия – определять версию Windows по версии библиотеки kernel32.dll,
которая совпадает с версией Windows. На моем компьютере с Windows 7 это выглядит так:
Также для решения задачи можно прибегнуть к помощи WMI.
uses
ActiveX, ComObj{, Variants};
procedure TForm1.Button1Click(Sender: TObject);
const
WbemUser = '';
WbemPassword = '';
WbemComputer = 'localhost';
wbemFlagForwardOnly = $00000020;
var
FSWbemLocator: OLEVariant;
FWMIService: OLEVariant;
FWbemObjectSet: OLEVariant;
FWbemObject: OLEVariant;
oEnum: IEnumvariant;
iValue: LongWord;
begin
try
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
FWMIService := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
FWbemObjectSet := FWMIService.ExecQuery('SELECT * FROM Win32_OperatingSystem', 'WQL', wbemFlagForwardOnly);
oEnum := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
while oEnum.Next(1, FWbemObject, iValue) = 0 do
begin
ShowMessage(Format('Версия: %s',[String(FWbemObject.Version)]));
FWbemObject := Unassigned;
end;
except
on E: EOleException do
ShowMessage(Format('EOleException %s (%x)', [E.Message, E.ErrorCode]));
on E: Exception do
ShowMessage(E.Classname + ':' + E.Message);
end;
end;
//initialization
// CoInitialize(nil);
//finalization
// CoUninitialize;
|
Можно прибегнуть к помощи функции NetServerGetInfo, возвращающей текущие сведения о конфигурации для указанного сервера
(если параметр ServerName равен nil, используется локальный компьютер).
type
NET_API_STATUS = DWORD;
_SERVER_INFO_101 = record
sv101_platform_id: DWORD;
sv101_name: LPWSTR;
sv101_version_major: DWORD;
sv101_version_minor: DWORD;
sv101_type: DWORD;
sv101_comment: LPWSTR;
end;
SERVER_INFO_101 = _SERVER_INFO_101;
PSERVER_INFO_101 = ^SERVER_INFO_101;
LPSERVER_INFO_101 = PSERVER_INFO_101;
function NetServerGetInfo(servername: LPWSTR; level: DWORD; var bufptr): NET_API_STATUS;
stdcall; external 'Netapi32.dll';
function NetApiBufferFree(Buffer: LPVOID): NET_API_STATUS; stdcall; external 'Netapi32.dll';
const
MAJOR_VERSION_MASK = $0F;
procedure TForm1.Button1Click(Sender: TObject);
var
Buffer: PSERVER_INFO_101;
begin
if NetServerGetInfo(nil, 101, Buffer) = NO_ERROR then
try
// Выдает данные реальной ОС из режима совместимости
ShowMessage(Format('NetServerGetInfo: %d.%d',
[Buffer.sv101_version_major and MAJOR_VERSION_MASK, Buffer.sv101_version_minor]));
finally
NetApiBufferFree(Buffer);
end;
end;
|
Или воспользоваться функцией NetWkstaGetInfo:
type
WKSTA_INFO_100 = record
wki100_platform_id: DWORD;
wki100_computername: LPWSTR;
wki100_langroup: LPWSTR;
wki100_ver_major: DWORD;
wki100_ver_minor: DWORD;
end;
LPWKSTA_INFO_100 = ^WKSTA_INFO_100;
function NetWkstaGetInfo(ServerName: LPWSTR; Level: DWORD;
BufPtr: Pointer): Longint; stdcall;
external 'netapi32.dll' Name 'NetWkstaGetInfo';
function GetWindowsVersion: string;
var
PBuf: LPWKSTA_INFO_100;
Res: LongInt;
begin
Result := '';
Res := NetWkstaGetInfo(nil, 100, @PBuf);
if Succeeded(Res) then
Result := Format('%d.%d', [PBuf^.wki100_ver_major, PBuf^.wki100_ver_minor]);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(GetWindowsVersion);
end;
|
Однако мне больше всего нравится способ с использованием PEB — структуры процесса в windows, заполняемой загрузчиком
на этапе создания процесса, которая содержит информацию об окружении, загруженных модулях (LDR_DATA), базовой
информации по текущему модулю и другие критичные данные, необходимые для функционирования процесса. Помимо всего
прочего в PEB содержится и версия Windows, которую и нужно получить.
Адрес PEB - константа для всех процессов в системе. На 32 битных Windows указатель на PEB хранится в сегментном регистре
FS:[0x30], а на 64 битных Windows в сегментном регистре GS:[0x60]. Получаем версию Windows из PEB:
function GetVersionPEB: Word;
asm
{$IFDEF WIN32}
mov edx, fs:[30h]
mov eax, [edx+0A4h]
shl eax, 8
mov al, [edx+0A8h]
{$ELSE IFDEF WIN64}
mov rdx, qword ptr GS:[abs $60]
mov eax, [rdx+118h]
shl eax, 8
mov al, [rdx+11Ch]
{$ENDIF}
end;
|
Теперь выделить из результата мажорный и минорный номер – дело техники. Некоторые из этих техник хотелось бы продемонстрировать.
procedure TForm1.Button1Click(Sender: TObject);
var
WinVer: Word;
Major, Minor: Byte;
begin
WinVer := GetVersionPEB;
Major := HiByte(WinVer);
Minor := LoByte(WinVer);
ShowMessage(IntToStr(Major) + '.' + IntToStr(Minor));
end;
|
Это, пожалуй, самый очевидный вариант, не нуждающийся в комментариях. А вот следующие будут интереснее.
procedure TForm1.Button2Click(Sender: TObject);
type
TWinVer = record
case Integer of
0: (Version: Word);
1: (Minor, Major: Byte);
end;
var
WinVer: TWinVer;
begin
WinVer.Version := GetVersionPEB;
ShowMessage(IntToStr(WinVer.Major) + '.' + IntToStr(WinVer.Minor));
end;
|
В этом примере используется запись с вариантами, состоящей из необязательной фиксированной и вариантной частей.
Вариантная часть напоминает условный оператор case. Между словами case и of записывается поле признака. Оно
определяет, какой из вариантов в данный момент будет активизирован. Поле признака должно быть равно одному из
расположенных следом значений, каждому из которых сопоставляется вариант записи. Он заключается в круглые скобки
и отделяется от своего значения двоеточием. Вариантная часть не имеет отдельного end (как этого бывает у оператора
case), слово end завершает и вариантную часть, и всю запись.
procedure TForm1.Button3Click(Sender: TObject);
type
TWinVer = record
Minor, Major: Byte;
end;
var
Ver: Word;
WinVer: TWinVer absolute Ver;
begin
Ver := GetVersionPEB;
ShowMessage(IntToStr(WinVer.Major) + '.' + IntToStr(WinVer.Minor));
end;
|
Здесь использовано ключевое слово absolute, указывающее компилятору рассматривать одну или несколько переменных как
существующие в одном месте в памяти (вроде вариантных записей), когда одна переменная перекрывает другую.
Вот теперь то мы точно будем знать с какой версией Windows имеем дело. Я же на этом заканчиваю. Всем творческих успехов!
.: GetVersionEx.zip :.
.: Пример к данной статье :.
|
При использовании материала - ссылка на сайт обязательна
|
|