CVersionUtils.h
#pragma once#include <wtypesbase.h>
#include <tchar.h>
#include <string>
#include <map>#ifdef _UNICODE
using _tstring = std::wstring;
#else
using _tstring = std::string;
#endifnamespace CVersionUtils
{// https://learn.microsoft.com/zh-cn/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo// 版本号结构typedef union _VERSON_NUMBER {DWORD Data;struct {WORD wLow; // 版本号低16位WORD wHigh; // 版本号高16位}Version;_VERSON_NUMBER() : Data(0) {}}VERSON_NUMBER;typedef struct _VS_VER_FIXEDFILEINFO {DWORD dwSignature; // 0xFEEF04BDVERSON_NUMBER dwStrucVersion; // 此结构的二进制版本号。 此成员的高序字包含主版本号,低序字包含次要版本号。VERSON_NUMBER dwFileVersionMS; // 文件二进制版本号中最重要的 32 位。 此成员与 dwFileVersionLS 一起使用,形成用于数字比较的 64 位值。VERSON_NUMBER dwFileVersionLS; // 文件二进制版本号中最低有效 32 位。 此成员与 dwFileVersionMS 一起使用,形成用于数字比较的 64 位值。VERSON_NUMBER dwProductVersionMS; // 分发此文件的产品的二进制版本号中最重要的 32 位。 此成员与 dwProductVersionLS 一起使用,形成用于数字比较的 64 位值。VERSON_NUMBER dwProductVersionLS; // 分发此文件的产品的二进制版本号中最低有效 32 位。 此成员与 dwProductVersionMS 一起使用,形成用于数字比较的 64 位值。DWORD dwFileFlagsMask; // 包含指定 dwFileFlags 中的有效位的位掩码。 仅当在创建文件时定义位时,位才有效。struct {DWORD fVS_FF_DEBUG : 1; // 该文件包含调试信息,或者在启用调试功能的情况下进行编译。DWORD fVS_FF_PRERELEASE : 1; // 该文件是开发版本,而不是商业发布的产品。DWORD fVS_FF_PRIVATEBUILD : 1; // 文件不是使用标准发布过程生成的。 如果设置了此标志, StringFileInfo 结构应包含 PrivateBuild 条目。DWORD fVS_FF_PATCHED : 1; // 该文件已修改,与同一版本号的原始发货文件不同。DWORD fVS_FF_INFOINFERRED : 1; // 文件的版本结构是动态创建的;因此,此结构中的某些成员可能为空或不正确。 切勿在文件的 VS_VERSIONINFO 数据中设置此标志。DWORD fVS_FF_SPECIALBUILD : 1; // 该文件由原始公司使用标准发布过程生成,但是相同版本号的正常文件的变体。 如果设置了此标志, StringFileInfo 结构应包含 SpecialBuild 条目。DWORD fVS_FF_Reserved : 26; // 保留未使用}dwFileFlags; // 包含指定文件的布尔属性的位掩码。union _FILE_OS {DWORD dwData;struct {enum eFileOSLo : WORD {eVOS_LO_UNKNOWN = 0x0000L, // 系统不知道设计该文件的操作系统eVOS_LO_WINDOWS16 = 0x0001L, // 该文件是为 16 位 Windows 设计的eVOS_LO_PM16 = 0x0002L, // 该文件是为 16 位 Presentation Manager 设计的eVOS_LO_PM32 = 0x0003L, // 该文件是为 32 位 Presentation Manager 设计的eVOS_LO_WINDOWS32 = 0x0004L, // 该文件专为 32 位 Windows 设计}Low;enum eFileOSHi : WORD {eVOS_HI_UNKNOWN = 0x0000L, // 系统不知道设计该文件的操作系统eVOS_HI_DOS = 0x0001L, // 该文件是针对 MS-DOS 设计的eVOS_HI_OS216 = 0x0002L, // 该文件是为 16 位 OS/2 设计的eVOS_HI_OS232 = 0x0003L, // 该文件是为 32 位 OS/2 设计的eVOS_HI_NT = 0x0004L, // 该文件是为 Windows NT 设计的}High;};}dwFileOS; // 为其设计此文件的操作系统enum eFileType : DWORD {eVFT_UNKNOWN = 0x00000000L, // 系统不知道文件类型eVFT_APP = 0x00000001L, // 该文件包含一个应用程序eVFT_DLL = 0x00000002L, // 该文件包含一个 DLLeVFT_DRV = 0x00000003L, // 该文件包含设备驱动程序eVFT_FONT = 0x00000004L, // 该文件包含字体eVFT_VXD = 0x00000005L, // 该文件包含一个虚拟设备eVFT_STATIC_LIB = 0x00000007L // 该文件包含一个静态链接库}dwFileType; // 文件的常规类型union {// 如果 dwFileTypeVFT_FONT, 则 dwFileSubtype 可以是以下值之一。enum eFileSubtypeDrv : DWORD {eVFT2_DRV_UNKNOWN = 0x00000000L, //系统未知驱动程序类型。eVFT2_DRV_PRINTER = 0x00000001L, //文件包含打印机驱动程序。eVFT2_DRV_KEYBOARD = 0x00000002L, //文件包含键盘驱动程序。eVFT2_DRV_LANGUAGE = 0x00000003L, //文件包含语言驱动程序。eVFT2_DRV_DISPLAY = 0x00000004L, //文件包含显示驱动程序。eVFT2_DRV_MOUSE = 0x00000005L, //文件包含鼠标驱动程序。eVFT2_DRV_NETWORK = 0x00000006L, //文件包含网络驱动程序。eVFT2_DRV_SYSTEM = 0x00000007L, //文件包含系统驱动程序。eVFT2_DRV_INSTALLABLE = 0x00000008L, //文件包含可安装的驱动程序。eVFT2_DRV_SOUND = 0x00000009L, //该文件包含声音驱动程序。eVFT2_DRV_COMM = 0x0000000AL, //文件包含通信驱动程序。eVFT2_DRV_VERSIONED_PRINTER = 0x0000000CL, //文件包含版本控制打印机驱动程序。}dwFileTypeVFT_DRV; // 驱动文件类型// 如果 dwFileTypeVFT_VXD, 则 dwFileSubtype 包含虚拟设备控制块中包含的虚拟设备标识符。enum eFileSubtypeFont : DWORD {eVFT2_FONT_UNKNOWN = 0x00000000L, //系统未知字体类型。eVFT2_FONT_RASTER = 0x00000001L, //文件包含光栅字体。eVFT2_FONT_TRUETYPE = 0x00000003L, //文件包含 TrueType 字体。eVFT2_FONT_VECTOR = 0x00000002L, //文件包含矢量字体。}dwFileTypeVFT_FONT; // 字体文件类型}dwFileSubtype; // 文件的子类型DWORD dwFileDateMS; // 文件的 64 位二进制创建日期和时间戳中最高有效 32 位。DWORD dwFileDateLS; // 文件的 64 位二进制创建日期和时间戳的最低有效 32 位。} VS_VER_FIXEDFILEINFO, * PVS_VER_FIXEDFILEINFO;// 语言与代码页 https://learn.microsoft.com/zh-cn/windows/win32/menurc/varfileinfo-blocktypedef struct _LANGANDCODEPAGE {WORD wLanguage;WORD wCodePage;_LANGANDCODEPAGE() : wLanguage(0), wCodePage(0) {}bool operator < (const _LANGANDCODEPAGE& r) const;bool operator == (const _LANGANDCODEPAGE& r) const;}LANGANDCODEPAGE, * PLANGANDCODEPAGE;// 版本号辅助类class CVersionNumber{public:CVersionNumber();CVersionNumber(const _tstring& strVer);CVersionNumber(WORD v1, WORD v2, WORD v3, WORD v4);CVersionNumber& operator = (const _tstring& strVer);_tstring GetString() const;bool IsEmpty() const;void Clear();bool operator == (const CVersionNumber& ref);bool operator != (const CVersionNumber& ref);bool operator < (const CVersionNumber& ref);bool operator <= (const CVersionNumber& ref);bool operator > (const CVersionNumber& ref);bool operator >= (const CVersionNumber& ref);private:int _Compare(const CVersionNumber& ref) const;private:WORD m_nVer[4]; //版本号};// PE文件信息typedef struct _VERSION_INFO{LANGANDCODEPAGE langAndCodePage; // 语言代码页_tstring strLanguageName; // 语言名_tstring strComments; // 文件注释_tstring strInternalName; // 内部名称_tstring strProductName; // 产品名称_tstring strCompanyName; // 公司名称_tstring strLegalCopyright; // 法律版权_tstring strProductVersion; // 产品版本_tstring strFileDescription; // 文件描述_tstring strLegalTrademarks; // 合法商标_tstring strPrivateBuild; // 私有构建_tstring strFileVersion; // 文件版本_tstring strOriginalFilename; // 原始文件名_tstring strSpecialBuild; // 特殊构建_tstring strFileVersionEx; // 文件版本(从 VS_FIXEDFILEINFO 中获取)_tstring strProductVersionEx; // 产品版本(从 VS_FIXEDFILEINFO 中获取)CVersionNumber FileVerNumber; // 文件版本号CVersionNumber ProductVerNumber; // 产品版本号VS_VER_FIXEDFILEINFO vsFixedInfo; // 固定文件信息}VERSION_INFO;// 文件版本信息列表using VERSION_LIST = std::map<LANGANDCODEPAGE, VERSION_INFO>;//// @brief: 获取文件版本列表// @param: strFile 文件路径// @param: fLocalised 本地化// @ret: VERSION_LIST 文件版本信息列表VERSION_LIST GetFileVersionList(const _tstring& strFile, bool fLocalised = true);//// @brief: 获取文件版本// @param: strFile 文件路径// @param: fLocalised 本地化// @ret: VERSION_INFO 文件版本信息VERSION_INFO GetFileVersion(const _tstring& strFile, bool fLocalised = true);
}
CVersionUtils.cpp
#include "CVersionUtils.h"
#include <winver.h>
#include <strsafe.h>#pragma comment(lib, "Version.lib")namespace CVersionUtils
{#pragma pack(push)
#pragma pack(1)// 包含文件的版本信息。 此信息与语言和代码页无关// https://learn.microsoft.com/zh-cn/windows/win32/menurc/vs-versioninfotypedef struct {WORD wLength; // VS_VERSIONINFO 结构的长度(以字节为单位),此长度不包括在 32 位边界上对齐任何后续版本资源数据的填充WORD wValueLength; // Value 成员的长度(以字节为单位)WORD wType; // 版本资源中的数据类型, 1: 资源包含文本数据 0: 版本资源包含二进制数据WCHAR szKey[15]; // Unicode 字符串 L“VS_VERSION_INFO”WORD Padding1; // 在 32 位边界上对齐 Children 成员所需的任意或零个 WORD//VS_FIXEDFILEINFO Value//WORD Padding2//WORD Children} VS_VERSIONINFO, * PVS_VERSIONINFO;#pragma pack(pop)CVersionNumber::CVersionNumber():m_nVer{ 0 }{};CVersionNumber::CVersionNumber(const _tstring& strVer):m_nVer{ 0 }{_stscanf_s(strVer.c_str(), _T("%hd.%hd.%hd.%hd"), &m_nVer[0], &m_nVer[1], &m_nVer[2], &m_nVer[3]);}CVersionNumber::CVersionNumber(WORD v1, WORD v2, WORD v3, WORD v4):m_nVer{ v1, v2, v3, v4 }{}CVersionNumber& CVersionNumber::operator = (const _tstring& strVer){_stscanf_s(strVer.c_str(), _T("%hd.%hd.%hd.%hd"), &m_nVer[0], &m_nVer[1], &m_nVer[2], &m_nVer[3]);return *this;}int CVersionNumber::_Compare(const CVersionNumber& ref) const{for (int i = 0; i < _countof(m_nVer); i++){if (m_nVer[i] != ref.m_nVer[i]){return (m_nVer[i] > ref.m_nVer[i] ? 1 : -1);}}return 0;}_tstring CVersionNumber::GetString() const{TCHAR szBuf[MAX_PATH] = { 0 };(void)::StringCchPrintf(szBuf, _countof(szBuf), _T("%hd.%hd.%hd.%hd"),m_nVer[0],m_nVer[1],m_nVer[2],m_nVer[3]);return szBuf;}bool CVersionNumber::IsEmpty() const{for (const auto& item : m_nVer){if (0 != item){return false;}}return true;}void CVersionNumber::Clear(){for (auto& item : m_nVer){item = 0;}}bool CVersionNumber::operator == (const CVersionNumber& ref){return _Compare(ref) == 0;}bool CVersionNumber::operator != (const CVersionNumber& ref){return _Compare(ref) != 0;}bool CVersionNumber::operator < (const CVersionNumber& ref){return _Compare(ref) < 0;}bool CVersionNumber::operator <= (const CVersionNumber& ref){return _Compare(ref) <= 0;}bool CVersionNumber::operator > (const CVersionNumber& ref){return _Compare(ref) > 0;}bool CVersionNumber::operator >= (const CVersionNumber& ref){return _Compare(ref) >= 0;}bool _LANGANDCODEPAGE::operator < (const _LANGANDCODEPAGE& r) const{if (this->wLanguage < r.wLanguage){return true;}if (this->wCodePage < r.wCodePage){return true;}return false;}bool _LANGANDCODEPAGE::operator == (const _LANGANDCODEPAGE& r) const{return this->wLanguage == r.wLanguage && this->wCodePage == r.wCodePage;}static VERSION_LIST _GetFileVersionList(const _tstring& strFile, bool fLocalised){VERSION_LIST infoResult;PVOID pFsRedirectionOldValue = NULL;bool isDisableWow64Fs = false;UINT cbTranslate = 0;DWORD dwVerSize;LPVOID lpVerData = NULL;// 加载文件if (strFile.empty()){return infoResult;}// 禁用文件重定向isDisableWow64Fs = ::Wow64DisableWow64FsRedirection(&pFsRedirectionOldValue);do{DWORD dwFlags = fLocalised ? FILE_VER_GET_LOCALISED : FILE_VER_GET_NEUTRAL;dwFlags |= FILE_VER_GET_PREFETCHED;// 获取版本信息数据大小dwVerSize = ::GetFileVersionInfoSize(strFile.c_str(), NULL);//dwVerSize = ::GetFileVersionInfoSizeEx(dwFlags, strFile.c_str(), NULL);if (0 == dwVerSize){break;}// 分配版本信息缓冲lpVerData = ::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, dwVerSize);if (!lpVerData){break;}// 获取版本信息BOOL fResult = FALSE;fResult = ::GetFileVersionInfo(strFile.c_str(), NULL, dwVerSize, lpVerData);//fResult = ::GetFileVersionInfoEx(dwFlags, strFile.c_str(), NULL, dwVerSize, lpVerData);if (!fResult){break;}// 获取语言代码LANGANDCODEPAGE* lpTranslate = NULL;if (!::VerQueryValue(lpVerData, _T("\\VarFileInfo\\Translation"), (LPVOID*)&lpTranslate, &cbTranslate)){break;}DWORD dwCount = cbTranslate / sizeof(LANGANDCODEPAGE);for (DWORD i = 0; i < dwCount; i++){LANGANDCODEPAGE langCodePage = lpTranslate[i];VERSION_INFO versionInfo;// 获取 VS_FIXEDFILEINFO 信息PVS_VERSIONINFO lpVersion = (PVS_VERSIONINFO)lpVerData;if (0 != lpVersion->wValueLength){VS_FIXEDFILEINFO* pFixedFileInfo = (VS_FIXEDFILEINFO*)((LPBYTE)lpVersion + sizeof(VS_VERSIONINFO));DWORD dwAlign = 4;DWORD_PTR dwPadding = ((DWORD_PTR)pFixedFileInfo % dwAlign);if (0 != dwPadding){pFixedFileInfo = (VS_FIXEDFILEINFO*)((LPBYTE)pFixedFileInfo + (dwAlign - dwPadding));}versionInfo.vsFixedInfo = *((PVS_VER_FIXEDFILEINFO)pFixedFileInfo);}// 查询所有信息// 查询单个信息函数auto _QueryInfo = [](const _tstring& strName, const LPVOID lpData, const LANGANDCODEPAGE& code) {TCHAR strQuery[MAX_PATH] = { 0 };LPCTSTR lpQueryRes = NULL;UINT uQueryCchSize;(void)::StringCchPrintf(strQuery, _countof(strQuery),_T("\\StringFileInfo\\%04x%04x\\%s"),code.wLanguage, code.wCodePage, strName.c_str());if (::VerQueryValue(lpData, strQuery, (LPVOID*)&lpQueryRes, &uQueryCchSize)){return lpQueryRes;}return _T("");};auto _GetVersionStr = [](VERSON_NUMBER hi, VERSON_NUMBER lo) -> _tstring {TCHAR szBuf[MAX_PATH] = { 0 };(void)::StringCchPrintf(szBuf, _countof(szBuf), _T("%hd.%hd.%hd.%hd"),hi.Version.wHigh,hi.Version.wLow,lo.Version.wHigh,lo.Version.wLow);return szBuf;};auto _GetLanguageNameStr = [](LANGANDCODEPAGE langCodePage) -> _tstring {TCHAR szBuf[MAX_PATH] = { 0 };::VerLanguageName(langCodePage.wLanguage, szBuf, _countof(szBuf));return szBuf;};versionInfo.langAndCodePage = langCodePage;versionInfo.strComments = _QueryInfo(_T("Comments"), lpVerData, langCodePage);versionInfo.strInternalName = _QueryInfo(_T("InternalName"), lpVerData, langCodePage);versionInfo.strProductName = _QueryInfo(_T("ProductName"), lpVerData, langCodePage);versionInfo.strCompanyName = _QueryInfo(_T("CompanyName"), lpVerData, langCodePage);versionInfo.strLegalCopyright = _QueryInfo(_T("LegalCopyright"), lpVerData, langCodePage);versionInfo.strProductVersion = _QueryInfo(_T("ProductVersion"), lpVerData, langCodePage);versionInfo.strFileDescription = _QueryInfo(_T("FileDescription"), lpVerData, langCodePage);versionInfo.strLegalTrademarks = _QueryInfo(_T("LegalTrademarks"), lpVerData, langCodePage);versionInfo.strPrivateBuild = _QueryInfo(_T("PrivateBuild"), lpVerData, langCodePage);versionInfo.strFileVersion = _QueryInfo(_T("FileVersion"), lpVerData, langCodePage);versionInfo.strOriginalFilename = _QueryInfo(_T("OriginalFilename"), lpVerData, langCodePage);versionInfo.strSpecialBuild = _QueryInfo(_T("SpecialBuild"), lpVerData, langCodePage);VS_VER_FIXEDFILEINFO& vsFixedInfo = versionInfo.vsFixedInfo;versionInfo.strFileVersionEx = _GetVersionStr(vsFixedInfo.dwFileVersionMS, vsFixedInfo.dwFileVersionLS);versionInfo.strProductVersionEx = _GetVersionStr(vsFixedInfo.dwProductVersionMS, vsFixedInfo.dwProductVersionLS);versionInfo.strLanguageName = _GetLanguageNameStr(langCodePage);versionInfo.FileVerNumber = versionInfo.strFileVersionEx.c_str();versionInfo.ProductVerNumber = versionInfo.strProductVersionEx.c_str();infoResult.emplace(langCodePage, versionInfo);}} while (false);// 恢复文件重定向if (isDisableWow64Fs){::Wow64RevertWow64FsRedirection(pFsRedirectionOldValue);}if (lpVerData){::HeapFree(::GetProcessHeap(), 0, lpVerData);lpVerData = NULL;}return infoResult;}VERSION_LIST GetFileVersionList(const _tstring& strFile, bool fLocalised/* = true*/){VERSION_LIST fileInfos = _GetFileVersionList(strFile, fLocalised);return fileInfos;}VERSION_INFO GetFileVersion(const _tstring& strFile, bool fLocalised/* = true*/){VERSION_INFO info;VERSION_LIST fileInfos = _GetFileVersionList(strFile, fLocalised);if (!fileInfos.empty()){info = fileInfos.begin()->second;}return info;}
}
main.cpp
#include <locale.h>
#include <tchar.h>
#include "Win32Utils/CVersionUtils.h"int _tmain(int argc, LPCTSTR argv[])
{setlocale(LC_ALL, "");CVersionUtils::VERSION_INFO info = CVersionUtils::GetFileVersion(_T("ntdll.dll"));return 0;
}