如何检测真实的Windows版本?

时间:2020-03-05 18:51:39  来源:igfitidea点击:

我知道我可以调用GetVersionEx Win32 API函数来检索Windows版本。在大多数情况下,返回的值反映了我的Windows版本,但有时并非如此。

如果用户在兼容性层下运行我的应用程序,则GetVersionEx不会报告真实版本,而是报告由兼容性层强制执行的版本。例如,如果我运行Vista并以" Windows NT 4"兼容模式执行程序,则GetVersionEx不会返回版本6.0,而是4.0。

有没有一种方法可以绕过此行为并获得真实的Windows版本?

解决方案

回答

WMI查询:

"Select * from Win32_OperatingSystem"

编辑:实际上更好是:

"Select Version from Win32_OperatingSystem"

我们可以这样在Delphi中实现:

function OperatingSystemDisplayName: string;

  function GetWMIObject(const objectName: string): IDispatch;
  var
    chEaten: Integer;
    BindCtx: IBindCtx;
    Moniker: IMoniker;
  begin
    OleCheck(CreateBindCtx(0, bindCtx));
    OleCheck(MkParseDisplayName(BindCtx, PChar(objectName), chEaten, Moniker));
    OleCheck(Moniker.BindToObject(BindCtx, nil, IDispatch, Result));
  end;

  function VarToString(const Value: OleVariant): string;
  begin
    if VarIsStr(Value) then begin
      Result := Trim(Value);
    end else begin
      Result := '';
    end;
  end;

  function FullVersionString(const Item: OleVariant): string;
  var
    Caption, ServicePack, Version, Architecture: string;
  begin
    Caption := VarToString(Item.Caption);
    ServicePack := VarToString(Item.CSDVersion);
    Version := VarToString(Item.Version);
    Architecture := ArchitectureDisplayName(SystemArchitecture);
    Result := Caption;
    if ServicePack <> '' then begin
      Result := Result + ' ' + ServicePack;
    end;
    Result := Result + ', version ' + Version + ', ' + Architecture;
  end;

var
  objWMIService: OleVariant;
  colItems: OleVariant;
  Item: OleVariant;
  oEnum: IEnumvariant;
  iValue: LongWord;

begin
  Try
    objWMIService := GetWMIObject('winmgmts:\localhost\root\cimv2');
    colItems := objWMIService.ExecQuery('SELECT Caption, CSDVersion, Version FROM Win32_OperatingSystem', 'WQL', 0);
    oEnum := IUnknown(colItems._NewEnum) as IEnumVariant;
    if oEnum.Next(1, Item, iValue)=0 then begin
      Result := FullVersionString(Item);
      exit;
    end;
  Except
    // yes, I know this is nasty, but come what may I want to use the fallback code below should the WMI code fail
  End;

  (* Fallback, relies on the deprecated function GetVersionEx, reports erroneous values
     when manifest does not contain supportedOS matching the executing system *)
  Result := TOSVersion.ToString;
end;

回答

我知道的最好方法是检查特定的API是否从某些DLL中导出。 Windows的每个新版本都添加了新功能,并且通过检查这些功能的存在,可以知道应用程序在哪个OS上运行。例如,Vista从kernel32.dll导出GetLocaleInfoEx,而以前的Windows没有。

长话短说,这是一个仅包含kernel32.dll导出的列表。

> *function: implemented in*  
> GetLocaleInfoEx:       Vista  
> GetLargePageMinimum:   Vista, Server 2003  
GetDLLDirectory:         Vista, Server 2003, XP SP1  
GetNativeSystemInfo:     Vista, Server 2003, XP SP1, XP  
ReplaceFile:             Vista, Server 2003, XP SP1, XP, 2000  
OpenThread:              Vista, Server 2003, XP SP1, XP, 2000, ME  
GetThreadPriorityBoost:  Vista, Server 2003, XP SP1, XP, 2000,     NT 4  
IsDebuggerPresent:       Vista, Server 2003, XP SP1, XP, 2000, ME, NT 4, 98   
GetDiskFreeSpaceEx:      Vista, Server 2003, XP SP1, XP, 2000, ME, NT 4, 98, 95 OSR2  
ConnectNamedPipe:        Vista, Server 2003, XP SP1, XP, 2000,     NT 4,                 NT 3  
Beep:                    Vista, Server 2003, XP SP1, XP, 2000, ME,       98, 95 OSR2, 95

编写确定实际OS版本的函数很简单;只需从最新的操作系统升级到最旧的操作系统,然后使用GetProcAddress检查导出的API。用任何一种语言来实现这一点都是微不足道的。

以下Delphi中的代码是从免费的DSiWin32库中提取的):

TDSiWindowsVersion = (wvUnknown, wvWin31, wvWin95, wvWin95OSR2, wvWin98,
  wvWin98SE, wvWinME, wvWin9x, wvWinNT3, wvWinNT4, wvWin2000, wvWinXP,
  wvWinNT, wvWinServer2003, wvWinVista);

function DSiGetWindowsVersion: TDSiWindowsVersion;
var
  versionInfo: TOSVersionInfo;
begin
  versionInfo.dwOSVersionInfoSize := SizeOf(versionInfo);
  GetVersionEx(versionInfo);
  Result := wvUnknown;
  case versionInfo.dwPlatformID of
    VER_PLATFORM_WIN32s: Result := wvWin31;
    VER_PLATFORM_WIN32_WINDOWS:
      case versionInfo.dwMinorVersion of
        0:
          if Trim(versionInfo.szCSDVersion[1]) = 'B' then
            Result := wvWin95OSR2
          else
            Result := wvWin95;
        10:
          if Trim(versionInfo.szCSDVersion[1]) = 'A' then
            Result := wvWin98SE
          else
            Result := wvWin98;
        90:
          if (versionInfo.dwBuildNumber = 73010104) then
             Result := wvWinME;
           else
             Result := wvWin9x;
      end; //case versionInfo.dwMinorVersion
    VER_PLATFORM_WIN32_NT:
      case versionInfo.dwMajorVersion of
        3: Result := wvWinNT3;
        4: Result := wvWinNT4;
        5:
          case versionInfo.dwMinorVersion of
            0: Result := wvWin2000;
            1: Result := wvWinXP;
            2: Result := wvWinServer2003;
            else Result := wvWinNT
          end; //case versionInfo.dwMinorVersion
        6: Result := wvWinVista;
      end; //case versionInfo.dwMajorVersion
    end; //versionInfo.dwPlatformID
end; { DSiGetWindowsVersion }

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 }

-更新2009-10-09

事实证明,在Vista SP1及更高版本上进行"未记录"的操作系统检测非常困难。查看API更改,可以发现Vista SP1中也实现了所有Windows 2008功能,而Windows 2008 R2中也实现了所有Windows 7功能。太糟糕了 :(

-更新结束

FWIW,这是我在实践中遇到的问题。我们(我工作的公司)所拥有的程序在Vista发行时(以及此后的数周……)还不是真正适用于Vista的程序。它也不在兼容层下工作。 (一些DirectX问题。不要问。)

我们不希望自己聪明的用户在所有兼容模式下都在Vista上运行此应用程序,所以我不得不寻找解决方案(比我聪明的人向我指出了正确的方向;这些东西以上不是我的想法)。现在,我将其发布为我们带来乐趣,并帮助所有将来需要解决此问题的可怜人。 Google,请为本文编索引!

如果我们有更好的解决方案(或者针对我的升级和/或者修复),请在此处发布答案...

回答

如何获取系统文件的版本?

最好的文件是kernel32.dll,位于%WINDIR%\ System32 \ kernel32.dll中。

有用于获取文件版本的API。例如:我正在使用Windows XP->" 5.1.2600.5512(xpsp.080413-2111)"

回答

另一个解决方案:

阅读以下注册表项:

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProductName

或者其他键

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion