65.9K
CodeProject 正在变化。 阅读更多。
Home

第一部分:克服 Windows 8.1 中 GetVersionEx 和 GetVersion API 的弃用

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (18投票s)

2013 年 11 月 5 日

CPOL

5分钟阅读

viewsIcon

150379

获取 Windows 8.1 上的 Windows 版本

引言

从 Windows 8.1 开始,您将无法再使用如下所示的代码块。

OSVERSIONINFOEX  versionInfo;
   versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
OSVERSIONINFO*  pVersionInfo = (POSVERSIONINFO)&versionInfo;   
if(::GetVersionEx(pVersionInfo))
{
  wcout<<_T("MajorVersion:")<<versionInfo.dwMajorVersion<<endl;
  wcout<<_T("MinorVersion:")<<versionInfo.dwMinorVersion<<endl;
}

请查看 更新 部分以获得更好的实现方式。

背景

我最近将我的开发机从 Windows 8 升级到了 Windows 8.1。我当时正尝试将 Windows 版本记录到产品的日志文件中,突然发现一个问题:从 GetVersionEx API 检索到的 Windows 版本实际上与 Windows 8 相关。也就是说,我期望的版本 6.3 = Windows 8.1,但我得到的是版本 6.2 = Windows 8。我不敢相信这样一个重要的 API 会出现故障,因此我使用 Hyper-V 从头创建了一个 Windows 8.1 映像,以确保这不是与 Windows 升级相关的问题。但问题依然存在,所以我升级到了 Visual Studio 2013,希望这能解决问题。问题依然存在,这时我发现 msdn 上有以下 文章,描述了 Microsoft 对 GetVersionEx API 的弃用。实际上,Visual Studio 2013 将 GetVersionEx 和 GetVersion API 标记为已弃用,并在您引用它们时抛出错误。此外,.Net Framework 的 API 用于检索操作系统版本也“已弃用”(返回不准确的数据)。就说这个举动是 Microsoft 的一个重大惊喜吧。网页浏览器应该如何报告准确的 User Agent?产品如何在不获取操作系统版本的情况下报告准确的遥测数据?在我这里,我该如何将操作系统版本记录到客户会发送给我的诊断日志文件中?所有这些问题都让我相信,Windows 8.1 SDK 附带的 Microsoft 版本辅助 API 并不足够。但它们确实提供了一个很好的线索,即 VerifyVersionInfo 可以用来填补空白。

我决定不使用动态的 LoadLibrary 和 GetProcAddress 技术来从 Kernelbase.dll、Kernel32.dll、shell32.dll 或 shcore.dll 中使用“未记录”的内部 API。从注册表中加载 Windows 版本也似乎不是最佳方法。另一种方法是加载 %systemroot%\system32 文件夹中的任何关键 DLL,然后使用 DllGetVersion 等 API 从该 DLL 中检索产品版本。但我也不喜欢这种方法,因为 DLL 本身可能会被热修复并可能具有不同的版本(我没有见过这种情况发生)。

使用代码

免责声明:这不是生产就绪代码!

如果您仔细查看 VerifyVersionInfo 及其姊妹 API VerSetConditionMask,会发现一个名为 VER_EQUAL 的有用标志。

我们可以使用 VER_EQUAL 标志来验证系统是否正在运行 Windows 8.1。

BOOL EqualsMajorVersion(DWORD majorVersion)
{
    OSVERSIONINFOEX osVersionInfo;
    ::ZeroMemory(&osVersionInfo, sizeof(OSVERSIONINFOEX));
    osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
    osVersionInfo.dwMajorVersion = majorVersion;
    ULONGLONG maskCondition = ::VerSetConditionMask(0, VER_MAJORVERSION, VER_EQUAL);
    return ::VerifyVersionInfo(&osVersionInfo, VER_MAJORVERSION, maskCondition);
}
BOOL EqualsMinorVersion(DWORD minorVersion)
{
    OSVERSIONINFOEX osVersionInfo;
    ::ZeroMemory(&osVersionInfo, sizeof(OSVERSIONINFOEX));
    osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
    osVersionInfo.dwMinorVersion = minorVersion;
    ULONGLONG maskCondition = ::VerSetConditionMask(0, VER_MINORVERSION, VER_EQUAL);
    return ::VerifyVersionInfo(&osVersionInfo, VER_MINORVERSION, maskCondition);
}

int _tmain(int argc, _TCHAR* argv[])
{
    if (EqualsMajorVersion(6) && EqualsMinorVersion(3))
    {
        wcout << _T("System is Windows 8.1");
    }
    return 0;
}

这有效,事实上,它与 Windows 8.1 SDK 中的 VersionHelpers.h 使用的机制非常相似。

ERSIONHELPERAPI
IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor)
{
    OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 };
    DWORDLONG        const dwlConditionMask = VerSetConditionMask(
        VerSetConditionMask(
        VerSetConditionMask(
            0, VER_MAJORVERSION, VER_GREATER_EQUAL),
               VER_MINORVERSION, VER_GREATER_EQUAL),
               VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);

    osvi.dwMajorVersion = wMajorVersion;
    osvi.dwMinorVersion = wMinorVersion;
    osvi.wServicePackMajor = wServicePackMajor;

    return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | 
      VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE;
}

所以您可能已经注意到,Windows 版本在历史上一直是可以预测的。我的意思是,我们有 Windows 3.0、Windows 3.1、Windows NT、95 (4.0)、Windows 2000 (5.0)、Windows XP (5.1)、...、Windows 7 (6.1)、...、Windows 8.1 (6.3)。不像上一个版本是 100.0,下一个版本是 213.0。所以公平地说,通过少量迭代和递增一个已知的基本版本,我们应该能够获得我们正在运行的 Windows 版本。

所以这里有三个额外的辅助函数,用于获取 Service Pack 并确定 Windows 是工作站还是 Windows Server。

BOOL EqualsServicePack(WORD servicePackMajor)
{
    OSVERSIONINFOEX osVersionInfo;
    ::ZeroMemory(&osVersionInfo, sizeof(OSVERSIONINFOEX));
    osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
    osVersionInfo.wServicePackMajor = servicePackMajor;
    ULONGLONG maskCondition = ::VerSetConditionMask(0, VER_SERVICEPACKMAJOR, VER_EQUAL);
    return ::VerifyVersionInfo(&osVersionInfo, VER_SERVICEPACKMAJOR, maskCondition);
}

BOOL EqualsProductType(BYTE productType)
{
    OSVERSIONINFOEX osVersionInfo;
    ::ZeroMemory(&osVersionInfo, sizeof(OSVERSIONINFOEX));
    osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
    osVersionInfo.wProductType = productType;
    ULONGLONG maskCondition = ::VerSetConditionMask(0, VER_PRODUCT_TYPE, VER_EQUAL);
    return ::VerifyVersionInfo(&osVersionInfo, VER_PRODUCT_TYPE, maskCondition);
}

BYTE GetProductType()
{
    if (EqualsProductType(VER_NT_WORKSTATION))
    {
        return VER_NT_WORKSTATION;
    }
    else if (EqualsProductType(VER_NT_SERVER))
    {
        return VER_NT_SERVER;
    }
    else if (EqualsProductType(VER_NT_DOMAIN_CONTROLLER))
    {
        return VER_NT_DOMAIN_CONTROLLER;
    }
    return 0;//unkown
}  

VerifyVersionInfo API 发布于 Windows 2000 (Windows 5.0),因此我们可以设置一个包含从 Windows 2000 开始的已知 Windows 版本的数组,并使用此 API 来验证它们。

struct WindowsNTOSInfo
{
    DWORD dwMajorVersion;
    DWORD dwMinorVersion;
    WORD wServicePackMajor;    
    //const TCHAR * pcszOSDisplayName;
};

struct WindowsNTOSInfoEx :WindowsNTOSInfo
{
    BYTE ProductType;
};

const WindowsNTOSInfo KnownVersionsOfWindows[] =
{
    { 6, 3, 0,   },//win8.1,server2012 r2
    { 6, 2, 0,   },//win8,server2012

    { 6, 1, 1,  },//win7,win2008r2 sp1
    { 6, 1, 0,  },//win7,win2008r2

    { 5, 1, 3,  },//winxp sp3
    { 5, 1, 2,  },//winxp sp2
    { 5, 1, 1,  },//winxp sp1
    { 5, 1, 0,  },//winxp

    { 6, 0, 2,  },//WinVista,server2008 SP2
    { 6, 0, 1,  },//WinVista,Server2008 Sp1
    { 6, 0, 0,  },//WinVista,Server2008

    { 5, 2, 2,  },//Windows Server 2003 Sp2
    { 5, 2, 1,  },//Windows Server 2003 Sp1
    { 5, 2, 0,  },//Windows Server 2003


    { 5, 1, 4,  }, //Windows Server 2000 Sp4
    { 5, 1, 3,  }, //Windows Server 2000 Sp3
    { 5, 1, 2,  }, //Windows Server 2000 Sp2
    { 5, 1, 2,  }, //Windows Server 2000 Sp1
    { 5, 1, 0,  }, //Windows Server 2000
};

const size_t n_KnownVersionofWindows = sizeof(KnownVersionsOfWindows) / sizeof(WindowsNTOSInfo);

所以这是检索已知 Windows 版本的代码。

bool GetKnownWindowsVersion(WindowsNTOSInfoEx& osInfo)
{
    for (size_t i = 0; i < n_KnownVersionofWindows; i++)
    {
        if (EqualsMajorVersion(KnownVersionsOfWindows[i].dwMajorVersion))
        {
            if (EqualsMinorVersion(KnownVersionsOfWindows[i].dwMinorVersion))
            {
                if (EqualsServicePack(KnownVersionsOfWindows[i].wServicePackMajor))
                {
                    osInfo.dwMajorVersion = KnownVersionsOfWindows[i].dwMajorVersion;
                    osInfo.dwMinorVersion = KnownVersionsOfWindows[i].dwMinorVersion;
                    osInfo.wServicePackMajor = KnownVersionsOfWindows[i].wServicePackMajor;
                    osInfo.ProductType = GetProductType();
                    return true;
                }
            }
        }
    }
    return false;
}

但对于检查未知版本的 Windows 呢?

嗯,为此我们需要编写一些循环并调用我们的辅助函数来验证主版本、次版本和服务包。

const DWORD MajorVersion_Start = 6;
const DWORD MinorVersion_Start = 3;
const WORD ServicePackVersion_Start = 1;

const DWORD MajorVersion_Max = 10;
const DWORD MinorVersion_Max = 5;
const WORD ServicePackVersion_Max = 4;

bool GetUnkownVersion(WindowsNTOSInfoEx& osInfo)
{
    DWORD minorVersionCounterSeed = MinorVersion_Start;
    DWORD servicePackCounterSeed = ServicePackVersion_Start;
    //by design, if we can't find the right service pack we will return true;
    for (DWORD majorVersion = MajorVersion_Start; majorVersion <= MajorVersion_Max; majorVersion++)
    {
        if (EqualsMajorVersion(majorVersion))
        {
            for (DWORD minorVersion = minorVersionCounterSeed; 
                    minorVersion <= MinorVersion_Max; minorVersion++)
            {
                if (EqualsMinorVersion(minorVersion))
                {
                    osInfo.dwMajorVersion = majorVersion;
                    osInfo.dwMinorVersion = minorVersion;
                    osInfo.ProductType = GetProductType();
                    for (WORD servicePack = servicePackCounterSeed; 
                           servicePack <= ServicePackVersion_Max; servicePack++)
                    {
                        if (EqualsServicePack(servicePack))
                        {                            
                            osInfo.wServicePackMajor = servicePack;
                            break;
                        }
                    }
                    return true;
                }
                else
                {
                    //reset servicepack version counter to 0 because
                    //we are going to increment our minor version.
                    servicePackCounterSeed = 0;
                }                
            }
        }
        else
        {
            //reset minor version to start from 0 because we are going to increment majorVersion;
            minorVersionCounterSeed = 0;
        }
    }
    return false;
}

那么,让我们用一个函数来调用前面提到的代码,将它们粘合在一起。

bool GetWindowsVersion(WindowsNTOSInfoEx& osInfo)
{
    bool capturedWinVersion = GetKnownWindowsVersion(osInfo);
    if (!capturedWinVersion)
    {
        return GetUnkownVersion(osInfo);
    }
    return capturedWinVersion;
}



int _tmain(int argc, _TCHAR* argv[])
{
    WindowsNTOSInfoEx osInfo;
    ::ZeroMemory(&osInfo,sizeof(WindowsNTOSInfoEx));
    if (GetWindowsVersion(osInfo))
    {
        wcout << _T("MajorVersion:") << osInfo.dwMajorVersion << 
            _T(" VersionMinor:") << osInfo.dwMinorVersion << 
            _T(" ServicePackMajor:") << osInfo.wServicePackMajor << 
            _T(" ProductType:") << osInfo.ProductType << endl;
    }
    else
    {
        wcout << _T("Failed to Detect Windows Version") << endl;
    }
    return 0;
}

注意:我只在 Windows 8.1 和 Windows XP SP3 上进行了冒烟测试。

关注点

性能:这段代码实际上相当慢!

在物理系统上,它可能需要 5-15 毫秒。在虚拟机上执行则需要 30 毫秒以上。由于该 API 在应用程序的生命周期中可能会被调用很多次,因此我包含了一个高分辨率计时器代码来衡量这是一个多长的操作。调用 VerifyVersionInfo 第一次时似乎成本很高。后续调用的成本似乎远低于第一次调用。

BuildNumber(内部版本号):我见过人们不太重视 Windows 的 BuildNumber,并且通过这种方法检索 BuildNumber 可能会非常昂贵。所以我跳过了 BuildNumber。

我将包含另一篇文章来获取 Windows 版本。但该方法使用 COM,可能不适合所有人。

 更新:

本论坛的一位用户提供了一种更好的方法。

如果您将嵌入式清单文件作为 .exe 程序的一部分包含进来。在 Windows 8.1 上,GetVersionEx 将返回正确的版本。请注意,您也可以支持其他操作系统。但您必须确保包含 Windows 8.1。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">

  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <!--This Id value indicates the application supports Windows 8.1 functionality-->
      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
      
    </application>
  </compatibility>

  <assemblyIdentity type="win32"
                    name="SampleCode.GetWindowsVersionInfo"
                    version="1.0.0.0"
                    processorArchitecture="x86"
                    publicKeyToken="0000000000000000"
  />
</assembly>

这种方法的优点是 GetVersionEx 的性能非常好。

但问题仍然存在,您的二进制文件在新版本的 Windows 上总是会表现不佳(因为您无法提前知道新版本的 Windows GUID)。

另外还有一个问题是,您无法创建在与早期 Windows OS 兼容模式下运行的应用程序。

您需要通过执行类似的操作来禁用 Visual Studio 的弃用错误。

	#pragma warning (disable : 4996)	
	versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
	POSVERSIONINFO pVersionInfo = (POSVERSIONINFO)&versionInfo;
	if (::GetVersionEx(pVersionInfo))
	{
		if (6 == versionInfo.dwMajorVersion && 3 == versionInfo.dwMinorVersion)
		{
			wcout << _T("Windows 8.1 Detected") << endl;
		}
		else if (6 == versionInfo.dwMajorVersion && 2 == versionInfo.dwMinorVersion)
		{
			wcout << _T("Windows 8.0 Detected") << endl;
		}		
	}
	#pragma warning (default : 4996)

 

我还发现了另一个 API,它仍然可以返回 Windows 版本而无需任何兼容性清单。但它的执行需要 2-3 毫秒,因此比 GetVersionEx API 慢得多。

 

bool GetWindowsVersion(DWORD& major, DWORD& minor)
{	
	LPBYTE pinfoRawData;
	if (NERR_Success == NetWkstaGetInfo(NULL, 100, &pinfoRawData))
	{
		WKSTA_INFO_100 * pworkstationInfo = (WKSTA_INFO_100 *)pinfoRawData;
		major = pworkstationInfo->wki100_ver_major;
		minor = pworkstationInfo->wki100_ver_minor;
		::NetApiBufferFree(pinfoRawData);
		return true;
	}
	return false;
}
int _tmain(int argc, _TCHAR* argv[])
{	
	DWORD major = 0;
	DWORD minor = 0;
	if (GetWindowsVersion(major, minor))
	{
		wcout << _T("Major:") << major << _T("Minor:") << minor << endl;
	}	
	return 0;
}

 

© . All rights reserved.