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;
#endif
namespace 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; // 0xFEEF04BD
VERSON_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, // 该文件包含一个 DLL
eVFT_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-block
typedef 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-versioninfo
typedef 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;
}