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






4.73/5 (18投票s)
获取 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;
}