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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (10投票s)

2013 年 11 月 5 日

CPOL

2分钟阅读

viewsIcon

36812

从 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 日:初始版本
© . All rights reserved.