[Из песочницы] Как узнать реальную версию Windows из режима совместимости
Думаю каждый хотя бы раз сталкивался с ситуацией, когда на современной ОС не удавалось запустить старую программу, и помогал в этом случае режим совместимости Windows.
В основе работы данного механизма лежит перехват различных функций и эмуляция их поведения, свойственного указанной версии Windows, например, эмулируются ключи реестра, каталоги с документами и прочее. Все это нужно для того, чтобы программа думала, что запущена в выбранной среде.
Если приложение запущено в режиме совместимости, то вызов GetVersionEx вернет фиктивную версию Windows, что, вероятно, не подойдет для системных программ типа твикеров ОС. Как быть в этом случае?
Анализ экспортируемых функцийНа просторах сети наткнулся на способ детектирования по наличию/отсутствию экспортируемых функций у системных библиотек. Пример кода: TDSiWindowsVersion = (wvUnknown, wvWin31, wvWin95, wvWin95OSR2, wvWin98, wvWin98SE, wvWinME, wvWin9x, wvWinNT3, wvWinNT4, wvWin2000, wvWinXP, wvWinNT, wvWinServer2003, wvWinVista); function DSiGetTrueWindowsVersion: TDSiWindowsVersion; function ExportsAPI (module: HMODULE; const apiName: string): boolean; begin Result:= GetProcAddress (module, PChar (apiName)) <> nil; end; { ExportsAPI } var hKernel32: HMODULE; begin { DSiGetTrueWindowsVersion } hKernel32:= GetModuleHandle ('kernel32'); Win32Check (hKernel32 <> 0); if ExportsAPI (hKernel32, 'GetLocaleInfoEx') then Result:= wvWinVista else if ExportsAPI (hKernel32, 'GetLargePageMinimum') then Result:= wvWinServer2003 else if ExportsAPI (hKernel32, 'GetNativeSystemInfo') then Result:= wvWinXP else if ExportsAPI (hKernel32, 'ReplaceFile') then Result:= wvWin2000 else if ExportsAPI (hKernel32, 'OpenThread') then Result:= wvWinME else if ExportsAPI (hKernel32, 'GetThreadPriorityBoost') then Result:= wvWinNT4 else if ExportsAPI (hKernel32, 'IsDebuggerPresent') then //is also in NT4! Result:= wvWin98 else if ExportsAPI (hKernel32, 'GetDiskFreeSpaceEx') then //is also in NT4! Result:= wvWin95OSR2 else if ExportsAPI (hKernel32, 'ConnectNamedPipe') then Result:= wvWinNT3 else if ExportsAPI (hKernel32, 'Beep') then Result:= wvWin95 else // we have no idea Result:= DSiGetWindowsVersion; end; { DSiGetTrueWindowsVersion } Решение интересное, но не считаю его приемлемым, так как с выходом каждой версии Windows требуется нетривиальная поддержка.
Используя WMI Из википедии Windows Management Instrumentation (WMI) в дословном переводе — это инструментарий управления Windows. Если говорить более развернутo, то WMI — это одна из базовых технологий для централизованного управления и слежения за работой различных частей компьютерной инфраструктуры под управлением платформы Windows.
Из WMI можно получить и версию Windows. Из документации следует что это можно сделать таким запросом: SELECT Version FROM Win32_OperatingSystem
Запустив WMI Explorer в режиме совместимости с Windows XP, можно увидеть, что это значение не эмулируется: 
Метод работает, более того, он полностью документирован, но медленный, и требует тянуть в проект кучу кода по работе с WMI.
Ищем в реестре
Пожалуй самый элегантный и правильный способ найденный в сети — это подсмотреть значение в реестре: HLKM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\CurrentVersionНу что же, попробуем:
with TRegistry.Create do
try
RootKey:= HKEY_LOCAL_MACHINE;
if OpenKeyReadOnly ('SOFTWARE\Microsoft\Windows NT\CurrentVersion') then
Edit1.Text:= ReadString ('CurrentVersion');
finally
Free;
end;
К сожалению, проверив его на Windows 7 оказалось, что этот ключ реестра эмулируется. Похоже в предыдущих версиях Windows этот способ работал, но, увы — сейчас этот трюк не сработает.Анализ версии kernel32.dll
Сам не проверял, но говорят, что версия файла у kernel32.dll совпадает с версией Windows. На моем компьютере с Windows 7 это так:
Вполне пригодный способ, но лично мне по непонятным причинам он не нравится, благо есть еще альтернатива.Анализируем PEB процесса
У каждого Windows-процесса есть структура описывающая его, называется она PEB. Она заполняется при старте процесса и содержит в себе адрес загрузки, список загруженных модулей, параметры командной строки, и, в том числе, версию Windows. Ниже пример модуля, используя который можно получить реальную версию Windows (тестировался на Delphi 2010 Win32):
unit RealWindowsVerUnit;
interface
uses
Windows;
var
//Реальная версия ОС, а не та что выдается системой при запуске
//в режиме совместимости
Win32MajorVersionReal: Integer;
Win32MinorVersionReal: Integer;
implementation
type
PPEB=^PEB;
PEB = record
InheritedAddressSpace: Boolean;
ReadImageFileExecOptions: Boolean;
BeingDebugged: Boolean;
Spare: Boolean;
Mutant: Cardinal;
ImageBaseAddress: Pointer;
LoaderData: Pointer;
ProcessParameters: Pointer; //PRTL_USER_PROCESS_PARAMETERS;
SubSystemData: Pointer;
ProcessHeap: Pointer;
FastPebLock: Pointer;
FastPebLockRoutine: Pointer;
FastPebUnlockRoutine: Pointer;
EnvironmentUpdateCount: Cardinal;
KernelCallbackTable: PPointer;
EventLogSection: Pointer;
EventLog: Pointer;
FreeList: Pointer; //PPEB_FREE_BLOCK;
TlsExpansionCounter: Cardinal;
TlsBitmap: Pointer;
TlsBitmapBits: array[0…1] of Cardinal;
ReadOnlySharedMemoryBase: Pointer;
ReadOnlySharedMemoryHeap: Pointer;
ReadOnlyStaticServerData: PPointer;
AnsiCodePageData: Pointer;
OemCodePageData: Pointer;
UnicodeCaseTableData: Pointer;
NumberOfProcessors: Cardinal;
NtGlobalFlag: Cardinal;
Spare2: array[0…3] of Byte;
CriticalSectionTimeout: LARGE_INTEGER;
HeapSegmentReserve: Cardinal;
HeapSegmentCommit: Cardinal;
HeapDeCommitTotalFreeThreshold: Cardinal;
HeapDeCommitFreeBlockThreshold: Cardinal;
NumberOfHeaps: Cardinal;
MaximumNumberOfHeaps: Cardinal;
ProcessHeaps: Pointer;
GdiSharedHandleTable: Pointer;
ProcessStarterHelper: Pointer;
GdiDCAttributeList: Pointer;
LoaderLock: Pointer;
OSMajorVersion: Cardinal;
OSMinorVersion: Cardinal;
OSBuildNumber: Cardinal;
OSPlatformId: Cardinal;
ImageSubSystem: Cardinal;
ImageSubSystemMajorVersion: Cardinal;
ImageSubSystemMinorVersion: Cardinal;
GdiHandleBuffer: array [0…33] of Cardinal;
PostProcessInitRoutine: Cardinal;
TlsExpansionBitmap: Cardinal;
TlsExpansionBitmapBits: array [0…127] of Byte;
SessionId: Cardinal;
end;
//Получить блок PEB своего процесса
function GetPDB: PPEB; stdcall;
asm
MOV EAX, DWORD PTR FS:[30h]
end;
initialization
//Получаем реальную версию ОС
Win32MajorVersionReal:= GetPDB^.OSMajorVersion;
Win32MinorVersionReal:= GetPDB^.OSMinorVersion;
end.
Скорость работы моментальная, ничего лишнего, единственное НО — недокументированная структура PEB, но как известно Microsoft очень заботится об обратной совместимости, так что с большой долей оптимизма можно считать, что раз описание структуры давно бродит по интернету, то в Microsoft она уже считается документированной.
Выводы А выводы простые, если очень нужно получить реальную версию то WMI отличный вариант, если же требуется легковесное решение — смотри в PEB.
