2017-05-21

正确地获取Windows的版本号

以前,想要获取Windows的版本号很简单,有个Win32 API函数名字叫做GetVersion,望文生义,接下来要做的事情就是去MSDN上查下用法就可以了。
现在,GetVersion会被报告成“过期函数”了。也许还能用,但(据MSDN说)起码在Win10上是别指望得到预期的结果了。道理也很简单,Win10都搞定期升级了,版本号规则肯定也和之前不一样了,你还指望这么老的函数能兼容么?
别痴心妄想了,GetVersionEx也一样过期。那么,眼下有什么好办法吗?

一般来说,拿Windows版本号可能有两种用途:

  • 我想看看你Windows版本达到我要求没。
  • 我就是想知道你Windows版本号是多少。

对于前者,微软现在在MSDN上是这样推荐的:它做了一组Version Helper functions,你如果想知道当前Windows的版本是不是某个特定的发行版,调这组函数就可以。我们来看看这组函数中三个典型:

  • IsWindowsXPOrGreater
  • IsWindowsXPSP3OrGreater
  • IsWindowsServer

不需要更多说明,我们从名字中就可以看出,这组函数可以用于判断Windows的大版本,Service Pack的版本(结合大版本),以及能知道是不是服务器版操作系统。通常情况下,这些函数大概是够了。

但是,有的时候我们并不关心版本号高低,我们只是想要一个版本号(例如记录日志时)而已。微软对此的建议是:用GetFileVersionInfo去获取一个系统DLL(例如Kernel32.dll)的文件版本号(原文看这里)。

相关的代码虽然能找到,MSDN上也有官方例子(有点小Bug),但比起一行GetVersion来代码量实在是不能算很少。由此可见,处理“过期函数”真的没有想象中那么容易。最后我还是提供一下我从项目代码中挖出来的一个实现吧。别照抄,如果你不想引入STL的话:
wstring GetOSVersion(const wstring& strWinSysDir) const
{
    wstring strWinSysFilePath = strWinSysDir + L"\\Kernel32.dll";
    DWORD dwVerInfoSize = GetFileVersionInfoSize(strWinSysFilePath.c_str(), nullptr);
    if (dwVerInfoSize)
    {
        vector<byte> vecVerData;
        vecVerData.resize(dwVerInfoSize);
        if (GetFileVersionInfo(strWinSysFilePath.c_str(), NULL, dwVerInfoSize, &vecVerData[0]))
        {
            LPCVOID pBlock = &vecVerData[0];

            UINT cbTranslate;
            TCHAR SubBlock[50];
            struct LANGANDCODEPAGE {
                WORD wLanguage;
                WORD wCodePage;
            } *lpTranslate;

            // Read the list of languages and code pages.
            VerQueryValue(pBlock,
                TEXT("\\VarFileInfo\\Translation"),
                (LPVOID*)&lpTranslate,
                &cbTranslate);

            // Read the file version for first language and code page.
            for (size_t i = 0; i < (cbTranslate / sizeof(struct LANGANDCODEPAGE)); ++i)
            {
                StringCchPrintf(SubBlock, sizeof(SubBlock) / sizeof(TCHAR),
                    L"\\StringFileInfo\\%04x%04x\\ProductVersion",
                    lpTranslate[i].wLanguage,
                    lpTranslate[i].wCodePage);

                LPVOID lpBuffer = nullptr;
                UINT dwBytes;
                if (VerQueryValue(pBlock, SubBlock, &lpBuffer, &dwBytes) && lpBuffer)
                {
                    wstring strOSVersion(reinterpret_cast<tchar>(lpBuffer));
                    return strOSVersion;
                }
            }
        }
    }
    return L"";
}

2 条评论:

  1. 回复
    1. 每天中午除了进餐剩下来的时间从加班改为写Blog,并且前段时间的加班积累了一些素材。

      删除