第二部分:克服 Windows 8.1 对 GetVersionEx 和 GetVersion API 的弃用






4.69/5 (10投票s)
从 WMI 获取 Windows 版本
引言
正如我在本系列文章的第一部分中所提到的,像这样的代码示例将始终返回不准确的数据。这是因为 GetVersionEx
和 GetVersionAPI
将始终指示存在 Windows 8.0,而不是 Windows 8.1 或更高版本。
OSVERSIONINFOEX versionInfo;
versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
POSVERSIONINFO pVersionInfo = (POSVERSIONINFO)&versionInfo;
if(::GetVersionEx(pVersionInfo))
{
//test to see if this is windows 8.1 fails
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;
}
}
在第一部分中,我提到了一种替代方法,涉及使用 COM。这种方法可能更容易适应在 .NET Framework 上使用,但这超出了本文的范围。这种方法有一个缺点——它涉及使用 WMI,这意味着 Windows 管理仪器不能被禁用。需要明确的是,如果WMI 服务停止,WMI 服务将自动启动。但是,如果您出于某种考虑决定禁用 WMI 服务,那么这种方法将不是检索 Windows 版本的可行方法。
鸣谢:我在此代码块中使用了一些 Microsoft WMI 代码示例。
背景
请参阅第一部分。
Using the Code
首先,我们需要确保 COM 被初始化和销毁。
class CComInitializer
{
public:
CComInitializer() :m_bInitialized(false)
{
HRESULT hres = CoInitializeEx(0, COINIT_MULTITHREADED);
m_bInitialized = SUCCEEDED(hres);
}
virtual ~CComInitializer()
{
if (m_bInitialized)
{
CoUninitialize();
m_bInitialized = false;
}
}
bool IsInitialized()const
{
return m_bInitialized;
}
bool m_bInitialized;
};
我们还需要一种将 Com Variant 转换为 STL string
的方法(可以使用 ATL 的 CComVariant
代替)。
wstring GetWmiPropertyValue
(LPCTSTR pcszPropertyName, CComPtr<IWbemClassObject> clsObj)
{
VARIANT vtProp;
wstring strPropValue;
HRESULT hr = clsObj->Get(pcszPropertyName, 0, &vtProp, 0, 0);
if (SUCCEEDED(hr))
{
if (vtProp.vt == VT_BSTR)
{
strPropValue = vtProp.bstrVal;
}
else if (vtProp.vt == VT_UI2)
{
TCHAR cszVersion[20] = { 0 };
_itow_s(vtProp.uiVal, &cszVersion[0], 20, 10);
strPropValue = cszVersion;
}
else if (vtProp.vt == VT_I4)
{
TCHAR cszVersion[20] = { 0 };
_itow_s(vtProp.uiVal, &cszVersion[0], 20, 10);
strPropValue = cszVersion;
}
VariantClear(&vtProp);
return strPropValue;
}
return _T("Error");
}
我们还需要一个函数,将以 null 结尾的 string
转换为一组 32 位值,这些值将代表版本。我相信这个函数还有很大的改进空间。
bool GetVersionFromString(LPCTSTR pcszVersion, DWORD&
majorVersion,DWORD& minorVersion,DWORD& buildNumber)
{
const TCHAR chVersionSeparator = '.';
majorVersion = 0;
minorVersion = 0;
buildNumber = 0;
const size_t nMaxVersionParts = 3;
const size_t versionMaxLen = 10;
const size_t versionMaxLenTerm = versionMaxLen + 1;
TCHAR szVersion[versionMaxLenTerm] = { 0 };
const TCHAR cszNullTerm = _T('\0');
if (pcszVersion != NULL)
{
size_t lenTotal = _tcslen(pcszVersion);
size_t counter = 0;
const TCHAR * pczSeparatorLocation = NULL;
size_t offset = 0;
do
{
counter++;
const TCHAR * pcszStartSearchFrom = pcszVersion + offset;
pczSeparatorLocation =
_tcschr(pcszStartSearchFrom, chVersionSeparator);
if (pczSeparatorLocation != NULL)
{
size_t len = pczSeparatorLocation - pcszStartSearchFrom;
_tcsnset_s(szVersion,
versionMaxLenTerm,cszNullTerm, versionMaxLenTerm);
_tcsncpy_s(szVersion,
versionMaxLenTerm, pcszStartSearchFrom, len);
if (cszNullTerm != szVersion)
{
int intValue = _tstoi(szVersion);
if (1 == counter)
{
majorVersion = intValue;
}
else if (2 == counter)
{
minorVersion = intValue;
}
else if (2 == counter)
{
buildNumber = intValue;
break;
}
}
else
{
break;
}
offset = (pczSeparatorLocation - pcszVersion)+1;
if (offset >= lenTotal)
{
break;
}
}
else
{
int intValue = _wtoi(pcszStartSearchFrom);
if (1 == counter)
{
majorVersion = intValue;
}
else if (2 == counter)
{
minorVersion = intValue;
}
else if (3 == counter)
{
buildNumber = intValue;
}
break;
}
} while (pczSeparatorLocation != NULL && (pcszVersion + lenTotal) >
pczSeparatorLocation && nMaxVersionParts > counter);
// buildnumber without major or minor version would make no sense.
return (majorVersion >0 || minorVersion >0);
}
return false;
}
最后,我们的 HelperFunction
初始化 WMI 服务客户端,执行查询并从 WMI 的 Win32_OperatingSystem
类中检索版本。 在此代码片段的末尾,我用粗体突出显示了一个部分,实际上那里发生了大部分“魔术”。 确保您不要从 Win32_OperatingSystem
类中检索超过您需要的数量,因为其他一些值(例如您的系统有多少可用 RAM)可能会降低速度。
bool GetWindowsVersionFromWMI(DWORD& majorVersion,
DWORD& minorVersion, DWORD& buildNumber)
{
majorVersion = 0;
minorVersion = 0;
buildNumber = 0;
//Com Init
CComInitializer comInitizlier;
if (!comInitizlier.IsInitialized())
{
return false;
}
HRESULT hres = ::CoInitializeSecurity(
NULL,
-1, // COM authentication
NULL, // Authentication services
NULL, // Reserved
RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication
RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
NULL, // Authentication info
EOAC_NONE, // Additional capabilities
NULL // Reserved
);
if (FAILED(hres))
{
wcout << "Failed to initialize security. Error code = 0x"
<< hex << hres << endl;
return false; // Program has failed.
}
// Obtain the initial locator to WMI -------------------------
CComPtr<IWbemLocator> wmiNamespaceLocator;
hres = ::CoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID *)&wmiNamespaceLocator);
if (FAILED(hres))
{
wcout << "Failed to create IWbemLocator object."
<< " Err code = 0x"
<< hex << hres << endl;
return false; // Program has failed.
}
// Step 4: -----------------------------------------------------
// Connect to WMI through the IWbemLocator::ConnectServer method
CComPtr<IWbemServices> wmiService = NULL;
// Connect to the root\cimv2 namespace with
// the current user and obtain pointer pSvc
// to make IWbemServices calls.
hres = wmiNamespaceLocator->ConnectServer(
_bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
NULL, // User name. NULL = current user
NULL, // User password. NULL = current
0, // Locale. NULL indicates current
NULL, // Security flags.
0, // Authority (for example, Kerberos)
0, // Context object
&wmiService // pointer to IWbemServices proxy
);
if (FAILED(hres))
{
wcout << "Could not connect. Error code = 0x"
<< hex << hres << endl;
return false; // Program has failed.
}
// Step 5: --------------------------------------------------
// Set security levels on the proxy -------------------------
hres = CoSetProxyBlanket(
wmiService, // Indicates the proxy to set
RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
NULL, // Server principal name
RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
NULL, // client identity
EOAC_NONE // proxy capabilities
);
if (FAILED(hres))
{
wcout << "Could not set proxy blanket. Error code = 0x"
<< hex << hres << endl;
return false; // Program has failed.
}
// Step 6: --------------------------------------------------
// Use the IWbemServices pointer to make requests of WMI ----
CComPtr<IEnumWbemClassObject> wmiClassEnumerator = NULL;
hres = wmiService->ExecQuery(
bstr_t("WQL"),
bstr_t("SELECT Version FROM Win32_OperatingSystem"),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&wmiClassEnumerator);
if (FAILED(hres))
{
wcout << "Query for operating system name failed."
<< " Error code = 0x"
<< hex << hres << endl;
return false; // Program has failed.
}
//get query Data
//no reason to loop through since we only one row worth of data.
if (wmiClassEnumerator)
{
ULONG uReturn = 0;
CComPtr<IWbemClassObject> clsObj;
HRESULT hr = wmiClassEnumerator->Next(WBEM_INFINITE, 1,
&clsObj, &uReturn);
if (0 == uReturn)
{
return false;
}
wstring strOSVersion = GetWmiPropertyValue(_T("Version"), clsObj);
//wcout << "
//OS Version : " << strOSVersion.c_str() << endl;
return GetVersionFromString(strOSVersion.c_str(),
majorVersion, minorVersion, buildNumber);
}
return false;
}
当然,还有我们的主函数,它将所有内容粘合在一起。
int _tmain(int argc, _TCHAR* argv[])
{
DWORD majorVersion=0, minorVersion=0,buildNumber=0;
if (GetWindowsVersionFromWMI(majorVersion, minorVersion, buildNumber))
{
wcout << "
OS MajorVersion : " << majorVersion << endl;
wcout << "
OS Minor : " << minorVersion << endl;
wcout << "
OS BuildNumber : " << buildNumber<< endl;
}
return 0;
}
关注点
假设我的高分辨率计时器代码是正确的,这种获取 Windows 版本的方法大约需要 30-70 毫秒,比第一部分慢 2-4 倍。 但是,如果您有一个已经使用 WMI 的应用程序,这可能值得考虑。
构建号:这种方法比第一部分有一个明显的优势,那就是您可以检索 Windows 构建号。
请注意,此代码只是一个示例,不应被视为生产就绪的代码。
历史
- 2013 年 11 月 5 日:初始版本