Windows平台可执行文件(execute文件)属性中会有版本信息,包含文件说明、文件版本、版权等信息。本文主要目的是将设置版本信息的方法公开化。
首先我们要清楚Windows下的可执行文件格式属于PE文件格式标准,PE文件标准支持内嵌资源,就是将一个外部文件内嵌到可执行文件中,这样程序启动时只需从自身内部找到这块资源加载就可以了,而不需依赖其他外部的磁盘文件。
PE文件支持的内嵌资源都有两个必须的标识:一个是资源类型,一个是资源名称。因此只要知道内嵌资源的这两个标识就能找到对应的资源。可执行文件的版本信息就是以内嵌资源的方式保存在文件中。
微软提供了一组通用API接口,用来获取和设置可执行文件的内嵌资源,请看下面详细介绍。
获取PE文件的版本信息数据有两种获取方法:
通用内嵌资源获取方式
通过下面接口可以获取Windows可执行文件中的任何通用资源。
#include
#include
std::string GetPEResource(const char* exepath, const char* type,
const char* name, int language = 0)
{
std::string r = "";
if (!exepath)
return r;
//判定文件是否存在
if (_access(exepath, 0) != 0)
return r;
//加载可执行文件
HMODULE hexe = LoadLibrary(exepath);
if (!hexe)
return r;
//查找资源
HRSRC src = FindResourceEx(hexe, type, name, language);
if (src)
{
HGLOBAL glb = LoadResource(hexe, src);
int sz = SizeofResource(hexe, src);
r = std::string((char *)LockResource(glb), sz);
UnlockResource(glb);
FreeResource(glb);
}
//释放可执行文件
FreeLibrary(hexe);
return r; //返回一个完整的、标准的版本信息数据
}
通过对 GetPEResource
接口进行简单调用即可获取PE文件中的版本数据。
文件的版本信息以内嵌资源的方式保存在可执行文件中,它的资源类型是RT_VERSION
(16),资源名称是 MAKEINTRESOURCE(VS_VERSION_INFO)
(1)。
std::string rc = GetPEResource( "C:\\测试文件.exe", RT_VERSION, MAKEINTRESOURCE(VS_VERSION_INFO), 0);
专用API获取方式
微软提供了另外一种专门用于获取文件版本信息的API接口,示例代码如下:
#include
#include
std::string GetPEVersionInfo(const char* exepath)
{
std::string r = "";
if (!exepath)
return r;
if (_access(exepath, 0) != 0)
return r;
DWORD sz = GetFileVersionInfoSize(exepath, 0);
r.resize(sz, 0);
return GetFileVersionInfo(exepath, 0, sz, (void*)r.c_str()) ? r : "";
}
通过调用此接口,也可正常获取文件中的版本信息数据。但此接口返回的缓冲区是自带备份的,并非标准版本数据。举个例子:假如某exe文件的版本数据大小是N字节,那么调用 GetPEVersionInfo
接口返回的缓冲区大小则是N * 2 + 4字节,前N字节数据为标准的版本数据,中间4字节用 ‘F’ ‘E’ ‘2’ 'X’字符填充,后N个字节用0填充。
版本数据是格式是一个名叫
VS_VERSIONINFO
的结构体,在msdn上可搜到这个结构体的相关描述。该结构可以认为由三部分构成。
第一部分是一个VS_FIXEDFILEINFO
对象,包含简要版本信息。
第二部分是一个VarFileInfo
对象,该部分可能不存在。
第三部分是一个StringFileInfo
对象,该部分可能不存在。
VS_FIXEDFILEINFO
对象存放在VS_VERSIONINFO
结构中的Value
字段中,
VarFileInfo
对象和StringFileInfo
对象(若存在的话)存放在VS_VERSIONINFO
结构中的Children
字段中。
typedef struct {
WORD wLength; // VS_VERSIONINFO 结构的长度(以字节为单位)
WORD wValueLength; // Value成员的长度(以字节为单位)。 如果没有与当前版本结构关联的 Value 成员,则此值为零。
WORD wType; // 版本资源中的数据的类型。 如果版本资源为文本数据,则此成员为 1; 如果版本资源为二进制数据,则为0。
WCHAR szKey; // 标识字符串: “VS_VERSION_INFO”
WORD Padding1; // 包含在32位边界上 对齐 Value 成员所需的 0 , 使后面字段按4字节对齐。
VS_FIXEDFILEINFO Value; // 与此 VS_VERSIONINFO 结构关联的数据。 wValueLength 成员指定此成员的长度;如果 wValueLength 为零,则不存在此成员。
WORD Padding2; // 在32位边界上对齐 Children 成员所需的 0 。 这些字节不包含在 wValueLength 中。
WORD Children; // 零个或一个 StringFileInfo 结构的数组,以及零个或一个 VarFileInfo 结构。
} VS_VERSIONINFO;
typedef struct tagVS_FIXEDFILEINFO {
DWORD dwSignature; // 文件填充信息标识,固定值0xFEEF04BD
DWORD dwStrucVersion; // 该结构的32位二进制版本号,高16位是主版本号,低16位是副版本号
DWORD dwFileVersionMS; // 该文件二进制版本号的高32bits
DWORD dwFileVersionLS; // 该文件二进制版本号的低32bits
DWORD dwProductVersionMS; // 发布该文件的产品二进制版本号高32bits
DWORD dwProductVersionLS; // 发布该文件的产品二进制版本号低32bits
DWORD dwFileFlagsMask; // 比特掩码,标志dwFileFlags的有效位
DWORD dwFileFlags;
//VS_FF_DEBUG---该文件包含调试信息或是由调试版编译的
//VS_FF_INFOINFERRED---文件的版本结构是动态创建的,因此,该结构中有的成员是空的或不正确的
//VS_FF_PATCHED---该文件被修改过
//VS_FF_PRERELEASE---该文件是开发版,不是商业发布版
//VS_FF_PRIVATEBUILD---该文件不是由标准发布步骤构建的
//VS_FF_SPECIALBUILD---该文件是由标准发布步骤构建的,但是相同版本号文件的变种
DWORD dwFileOS; // 用该文件的操作系统
DWORD dwFileType;
//文件类型:
// VFT_APP---文件包含一个应用程序
// VFT_DLL---文件包含一个DLL
// VFT_DRV---文件包含一个设备驱动
// VFT_FONT---文件包含一个字体文件
// VFT_STATIC_LIB---文件包含一个静态链接库
// VFT_UNKNOWN---文件类型未知
// VFT_VXD---文件包含一个虚拟设备
DWORD dwFileSubtype; // 文件的子类型,由dwFileType决定
DWORD dwFileDateMS; // 二进制文件创建日期和时间戳的高32bits
DWORD dwFileDateLS; // 二进制文件创建日期和时间戳的低32bits
} VS_FIXEDFILEINFO;
typedef struct {
WORD wLength; // 整个 VarFileInfo 块的长度(以字节为单位,包括 Children 成员指示 的所有 结构)。
WORD wValueLength; // 此成员始终等于零。
WORD wType; // 版本资源中的数据的类型。 如果版本资源为文本数据,则此成员为 1; 如果版本资源为二进制数据,则为0。
WCHAR szKey; // Unicode 字符串 L"VarFileInfo"。
WORD Padding; // 在32位边界上对齐 Children 成员所需的 0 , 使后面字段按4字节对齐。
Var Children; // 通常包含应用程序或 DLL 支持的语言列表信息。
} VarFileInfo;
typedef struct {
WORD wLength;
WORD wValueLength; // Value字段长度
WORD wType;
WCHAR szKey; // 标识字符串: “Translation”
WORD Padding;
DWORD Value; // 一个或多个值的数组,这些值是语言和代码页标识符对。
} Var;
typedef struct {
WORD wLength; // 整个 StringFileInfo 块的长度(以字节为单位)包括 Children 成员指示 的所有 结构。
WORD wValueLength; // 此成员始终等于零。
WORD wType;
WCHAR szKey; // Unicode 字符串 L"StringFileInfo"。
WORD Padding;
StringTable Children; // 一个或多个 StringTable 结构的 数组。 每个 StringTable 结构的 szKey 成员指示用于显示该 StringTable 结构中文本的适当语言和代码页。
} StringFileInfo;
typedef struct {
WORD wLength;
WORD wValueLength; // 此成员始终等于零。
WORD wType;
WCHAR szKey; // 作为 Unicode 字符串存储的8位十六进制数。 四个最有效的数字表示语言标识符。 四个最小有效位表示为其设置数据格式的代码页。 每个 Microsoft Standard Language 标识符包含两部分:低序位10位指定主要语言,高序位6位指定子语言。 有关有效标识符的表,请参阅。
WORD Padding;
String Children; // 一个或多个 String 结构的数组。
} StringTable;
typedef struct {
WORD wLength;
WORD wValueLength; // Value 成员的大小。
WORD wType;
WCHAR szKey; // 任意 Unicode 字符串。 szKey 成员可以是以下一个或多个值。 这些值仅作为准则。如:“Comments” “CompanyName” “FileDescription” "FileVersion"
WORD Padding;
WORD Value; // 以零结尾的字符串,与 szKey 成员对应的值。
} String;
理解版本数据的格式后,可以按照它的规则将文件版本数据进行重组。
BOOL CUpdateVersionInfo::UpdatePEVersion(CString strFile, CString strCode, CString strValue)
{
HMODULE hModule = LoadLibraryExW(strFile, NULL, DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE);
if (hModule == NULL)
return FALSE;
HRSRC hRsrc = FindResourceExW(hModule, RT_VERSION, MAKEINTRESOURCEW(1), klangCN);
if (hRsrc == NULL)
return FALSE;
HGLOBAL hGlobal = LoadResource(hModule, hRsrc);
if (hGlobal == NULL)
return FALSE;
void* pData = LockResource(hGlobal);
if (pData == NULL)
return FALSE;
DWORD size = SizeofResource(hModule, hRsrc);
if (size == 0)
return FALSE;
// 获取 VS_FIXEDFILEINFO
auto pVersionInfo = reinterpret_cast<const VS_VERSIONINFO*>(pData);
VS_FIXEDFILEINFO pFixedFileInfo;
if (pVersionInfo->Header.wValueLength > 0)
pFixedFileInfo = pVersionInfo->Value;
if (pFixedFileInfo.dwSignature != SIGNATURE)
{
pFixedFileInfo = { 0 };
pFixedFileInfo.dwSignature = SIGNATURE;
pFixedFileInfo.dwFileType = VFT_APP;
}
/
// 确定 Value or Children 的位置
const BYTE* fixedFileInfoEndOffset = reinterpret_cast<const BYTE*>(&pVersionInfo->szKey) + (wcslen(pVersionInfo->szKey) + 1) * sizeof(WCHAR) + pVersionInfo->Header.wValueLength;
const BYTE* pVersionInfoChildren = reinterpret_cast<const BYTE*>(Round(reinterpret_cast<ptrdiff_t>(fixedFileInfoEndOffset)));
size_t versionInfoChildrenOffset = pVersionInfoChildren - pData;
size_t versionInfoChildrenSize = pVersionInfo->Header.wLength - versionInfoChildrenOffset;
const auto childrenEndOffset = pVersionInfoChildren + versionInfoChildrenSize;
const auto resourceEndOffset = static_cast<BYTE*>(pData) + size;
/
// 获取 StringFileInfo and VarFileInfo
std::vector<VersionStringTable> stringTables;
std::vector<TRANSLATE> supportedTranslations;
for (auto p = pVersionInfoChildren; p < childrenEndOffset && p < resourceEndOffset;) {
auto pKey = reinterpret_cast<const VS_VERSION_STRING*>(p)->szKey;
auto versionInfoChildData = GetChildrenData(p);
if (wcscmp(pKey, L"StringFileInfo") == 0) {
DeserializeVersionStringFileInfo(versionInfoChildData.first, versionInfoChildData.second, stringTables);
}
else if (wcscmp(pKey, L"VarFileInfo") == 0) {
DeserializeVarFileInfo(versionInfoChildData.first, supportedTranslations);
}
p += Round(reinterpret_cast<const VS_VERSION_STRING*>(p)->Header.wLength);
}
if (stringTables.empty())
{
TRANSLATE translate = { klangCN, kCodePageCN };
stringTables.push_back({ translate });
supportedTranslations.push_back(translate);
}
if (strCode == "ProductVersion" || strCode == "FileVersion")
{
unsigned short ver[4] = { 0 };
CStringArray sArray;
this->SplitString(&sArray, strValue, _T("."));
for (size_t i = 0; i < sArray.GetSize(); i++)
{
CStringA s = CStringTochar(sArray[i]);
ver[i] = atoi(s);
}
if (strCode == "ProductVersion")
{
pFixedFileInfo.dwProductVersionMS = ver[0] << 16 | ver[1];
pFixedFileInfo.dwProductVersionLS = ver[2] << 16 | ver[3];
}
if (strCode == "FileVersion")
{
pFixedFileInfo.dwFileVersionMS = ver[0] << 16 | ver[1];
pFixedFileInfo.dwFileVersionLS = ver[2] << 16 | ver[3];
}
}
for (auto j = stringTables.begin(); j != stringTables.end(); ++j)
{
auto& stringPairs = j->strings;
for (auto k = stringPairs.begin(); k != stringPairs.end(); ++k)
{
if (k->first == strCode.AllocSysString())
{
k->second = strValue;
goto stop;
}
}
// Not found, append one for all tables.
stringPairs.push_back(VersionString(strCode, strValue));
}
stop:
FreeLibrary(hModule);
hModule = NULL;
// 重组数据
HANDLE hResource = BeginUpdateResource(strFile.AllocSysString(), FALSE);
if (NULL == hResource)
return FALSE;
VersionStampValue versionInfo;
versionInfo.key = L"VS_VERSION_INFO";
versionInfo.type = 0;
auto fixedsize = sizeof(VS_FIXEDFILEINFO);
versionInfo.valueLength = fixedsize;
auto& dst = versionInfo.value;
dst.resize(fixedsize);
memcpy(&dst[0], &pFixedFileInfo, fixedsize);
{
VersionStampValue stringFileInfo;
stringFileInfo.key = L"StringFileInfo";
stringFileInfo.type = 1;
stringFileInfo.valueLength = 0;
for (const auto& iTable : stringTables) {
VersionStampValue stringTableRaw;
stringTableRaw.type = 1;
stringTableRaw.valueLength = 0;
{
auto& translate = iTable.encoding;
std::wstringstream ss;
ss << std::hex << std::setw(8) << std::setfill(L'0') << (translate.wLanguage << 16 | translate.wCodePage);
stringTableRaw.key = ss.str();
}
for (const auto& iString : iTable.strings) {
const auto& stringValue = iString.second;
auto strLenNullTerminated = stringValue.length() + 1;
VersionStampValue stringRaw;
stringRaw.type = 1;
stringRaw.key = iString.first;
stringRaw.valueLength = strLenNullTerminated;
auto size = strLenNullTerminated * sizeof(WCHAR);
auto& dst = stringRaw.value;
dst.resize(size);
auto src = stringValue.c_str();
memcpy(&dst[0], src, size);
stringTableRaw.children.push_back(std::move(stringRaw));
}
stringFileInfo.children.push_back(std::move(stringTableRaw));
}
versionInfo.children.push_back(std::move(stringFileInfo));
}
{
VersionStampValue varFileInfo;
varFileInfo.key = L"VarFileInfo";
varFileInfo.type = 1;
varFileInfo.valueLength = 0;
{
VersionStampValue varRaw;
varRaw.key = L"Translation";
varRaw.type = 0;
{
auto newValueSize = sizeof(DWORD);
auto& dst = varRaw.value;
dst.resize(supportedTranslations.size() * newValueSize);
for (auto iVar = 0; iVar < supportedTranslations.size(); ++iVar) {
auto& translate = supportedTranslations[iVar];
auto var = DWORD(translate.wCodePage) << 16 | translate.wLanguage;
memcpy(&dst[iVar * newValueSize], &var, newValueSize);
}
varRaw.valueLength = varRaw.value.size();
}
varFileInfo.children.push_back(std::move(varRaw));
}
versionInfo.children.push_back(std::move(varFileInfo));
}
std::vector<BYTE> out = std::move(versionInfo.Serialize());
// 更新数据
if (!UpdateResourceW(hResource, RT_VERSION, MAKEINTRESOURCEW(1), klangCN,
&out[0], static_cast<DWORD>(out.size()))) {
return FALSE;
}
BOOL bResult = EndUpdateResourceW(hResource, FALSE);
return bResult ? TRUE : FALSE;
}
通过重组数据得到一个内存块,将这些数据呈现到exe可执行文件的属性中,就需将此数据注入到文件中
#include
bool SetPEResource(const char* exepath, const char* type, const char* name, const std::string& value, int language = 0)
{
HANDLE hexe = BeginUpdateResource(exepath, FALSE);
if (!hexe)
return false;
BOOL r = UpdateResource(hexe, type, name, language,
(LPVOID)value.c_str(), (DWORD)value.size());
BOOL er = EndUpdateResource(hexe, FALSE);
return (r && er);
}
无法解析的外部符号GetFileVersion…
#pragma comment(lib, “version.lib”)
BSTR
https://blog.csdn.net/qq_21232339/article/details/51220592
https://blog.csdn.net/shihuaguo/article/details/8541172
不进行数据重组的情况下修改,只能在原来的位数上修改,无法修改超过原本字段位数,若超过原本字段位数,后面的重要内容将会丢失
分享代码链接
COM接口以及测试程序 : https://download.csdn.net/download/sinat_38626955/85024858
rcedit开源 :
https://github.com/electron/rcedit
https://gitee.com/freeasm/rcedit.git