Windows PE文件的版本信息存于.rsrc节的RT_VERSION资源中,需解析VS_VERSIONINFO结构、严格对齐并重序列化后用UpdateResource写入。

PE版本资源结构在哪找
Windows PE文件的版本信息(Version Info)存放在资源节(.rsrc)中,类型为 RT_VERSION,名称通常是 1(即主版本块),语言ID通常为 0x409(美国英语)或 0x0(LANG_NEUTRAL)。它不是普通字符串表,而是一个嵌套的二进制结构:最外层是 VS_VERSIONINFO,内部包含 StringFileInfo 和 VarFileInfo,再往下才是实际键值对(如 FileVersion、ProductName)。
直接用 BeginUpdateResource/UpdateResource 修改版本资源几乎必然失败——因为这些API只接受完整、对齐、大小精确的资源数据块,而版本资源有严格的字节对齐要求(所有字段必须按 WORD 边界对齐,字符串必须双字节零终止且长度字段要重算),手动生成极易出错。
用 UpdateResource 替换整个版本块的实操要点
想稳妥修改,必须提取原始资源 → 解析结构 → 修改指定字符串 → 重新序列化 → 再写入。关键步骤如下:
- 用
FindResource+LoadResource获取原始RT_VERSION数据指针,注意返回的是未解压的原始内存块(含头部和子块) - 手动解析
VS_VERSIONINFO头部,定位到StringTable(在StringFileInfo下),找到目标String子块(例如键名为FileVersion的那一项) - 修改字符串内容时,必须保证新字符串的 UTF-16 长度(含结尾两个
\0)不超过原String块分配的空间;否则必须重建整个StringTable,并同步更新所有长度字段(wLength、wValueLength、dwSignature等) - 重新打包时,每个子结构起始地址必须是
WORD对齐(地址 % 2 == 0),末尾需补零填充;最终整个资源块大小必须是DWORD对齐(% 4 == 0) - 调用
UpdateResource时,第三个参数(lpData)必须指向完全重序列化后的新内存块,且第四个参数(cbData)必须是该块真实字节数(不能是原始大小)
// 示例:检查是否能原地替换(仅当新字符串更短或等长时安全)
if (newWStr.length() * sizeof(wchar_t) + 2 <= oldStringLengthField) {
// 可覆盖:memcpy + 补零
} else {
// 必须重建整个 VS_VERSIONINFO 结构
}
为什么 GetFileVersionInfo 读出来全是空或乱码
这不是编码问题,而是调用方式错误。常见陷阱包括:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
立即学习“C++免费学习笔记(深入)”;
- 忘记先调用
GetFileVersionInfoSize获取缓冲区大小,直接传一个固定小缓冲区,导致截断 - 传给
GetFileVersionInfo的缓冲区大小小于GetFileVersionInfoSize返回值,触发静默失败(函数返回 TRUE 但数据不全) - 解析时误把
VerQueryValue返回的指针当字符串直接printf,而它指向的是VS_FIXEDFILEINFO*或LPWSTR(取决于查询路径),后者还需转换为窄字符串 - 查询路径写错,例如用
\StringFileInfo\040904b0\ProductName,但实际语言块名是040904E4(含代码页)或04090000(LANG_NEUTRAL),必须从资源枚举中动态获取
第三方库(如 pe-tools 或 pefile)能省事吗
C++ 生态里没有成熟、轻量、免依赖的 PE 版本资源编辑库。Python 的 pefile 支持读取但不支持安全写入;C++ 的 pe-tools(GitHub 上几个同名项目)大多只读或接口不稳定。真正可靠的方案仍是自己实现解析器,重点守住三点:
- 所有长度字段(
wLength、wValueLength、szKey后的字符串长度)必须严格按规范重算 - 每个结构体起始偏移必须是偶数,结构间填充字节不可省略
- 修改后必须用
dumpbin /headers或CFF Explorer验证资源节校验和是否仍有效(虽然 Windows 不强制校验,但错位会导致资源加载失败)
最易被忽略的是:版本资源块内部的字符串键名(如 CompanyName)本身也是 UTF-16 字符串,且以双零结尾;如果手动拼接时漏掉一个 \0,整个后续结构都会偏移错乱。


















