我正在使用以下代码检索当前可执行文件的版本字符串:
// http://stackoverflow.com/questions/13941837/how-to-get-version-info-from-resources
std::string get_version_string()
{
auto hInst = GetModuleHandle(NULL);
// The following functions allocate persistent one-time space in the process's memory - they don't need to have results freed
auto hResInfo = FindResourceA(hInst, MAKEINTRESOURCE(1), RT_VERSION);
auto dwSize = SizeofResource(hInst, hResInfo);
auto hResData = LoadResource(hInst, hResInfo);
char *pRes = static_cast<char *>(LockResource(hResData));
if ( !dwSize || !pRes ) return {};
// Copy is required because VerQueryValue modifies the object, but LoadResource's resource is non-modifiable.
// SizeofResource yielded the size in bytes, according to its documentation.
std::vector<char> ResCopy(dwSize);
std::copy(pRes, pRes + dwSize, ResCopy.begin());
// https://stackoverflow.com/a/1174697/1505939
LPVOID pvFileVersion{};
UINT iFileVersionLen{};
if ( !VerQueryValueA(&ResCopy[0], "\\StringFileInfo\\040904E4\\FileVersion", &pvFileVersion, &iFileVersionLen) )
return "(unknown)"s;
char buf[200];
sprintf(buf, "%p\n%p\n%p", &ResCopy[0], &ResCopy[0] + ResCopy.size(), pvFileVersion);
debug_output ( buf );
auto s = static_cast<char *>(pvFileVersion);
return std::string( s, s + iFileVersionLen );
}
VerQueryValue文档以及有关此主题的其他SO问题表明,应该
VerQueryValue
将指针返回到资源块中。但是,我得到的输出是:000000000594b460
000000000594b748
000000000594b802
此外,如果将
ResCopy
的分配更改为std::vector<char> ResCopy(dwSize + 0x200);
,则得到输出:000000000594b460
000000000594b948
000000000594b802
我唯一可以得出的结论是,在原始情况下,
VerQueryValueA
函数正在执行越界写操作。它写的内容超出了SizeofResource
的大小范围;写在我的载体之外。即使该功能似乎正常运行,我怀疑这实际上可能是一个错误。
我的问题是:我做错什么了吗,或者这是
VerQueryValueA
中的错误吗?我该如何解决该问题?注意:如果我使用
VerQueryValueW
,那么它确实会首先在ResCopy
内部返回一个指针。This answer似乎暗示了这个问题,但是我没有使用
GetFileVersionInfo
(这需要文件名,似乎没有任何等效的函数使用模块句柄)。这样做的更大目的是能够在日志文件中记录我的应用程序的版本字符串,并且当我们显然已经将可执行文件加载为基于文件名的文件时,尝试查找和打开文件似乎是一堆更多的可能的失败点。运行它。
最佳答案
GetFileVersionInfo()
执行VerQueryValue()
依赖的修复程序和数据转换。 Raymond Chen甚至写了一篇关于它的博客文章:
The first parameter to VerQueryValue really must be a buffer you obtained from GetFileVersionInfo
VerQueryValue函数的文档指出,第一个参数是“指向包含GetFileVersionInfo函数返回的版本信息资源的缓冲区的指针”。但是,有些人决定绕过此步骤,并传递指向以其他方式获得的数据的指针,然后怀疑为什么VerQueryValue不起作用。
文档说,由于某种原因,VerQueryValue的第一个参数必须是GetFileVersionInfo函数返回的缓冲区。 GetFileVersionInfo返回的缓冲区是一个不透明的数据块,经过专门格式化,因此VerQueryValue将起作用。您不应该查看该缓冲区内部的内容,并且当然不能尝试“以其他方式获取数据”。因为如果这样做,VerQueryValue将在缓冲区中寻找某些未按照函数预期方式格式化的内容。
除了在资源数据的开头查询VS_FIXEDFILEINFO
之外,使用VerQueryValue()
从原始资源数据中查询其他版本数据确实是不安全的。该数据尚未准备好供VerQueryValue()
使用。与上面的文章一样,您链接到的问题的答案甚至说明了这一点:
如果从文档中还不够明显,您不能仅将指针传递给以其他方式获得的版本资源,那么一旦您看到the format of 32-bit version resources就会更明显。请注意,所有字符串都以Unicode存储。但是,如果您调用ANSI版本的VerQueryValueA来请求字符串,则该函数必须为您提供一个指向ANSI字符串的指针。原始版本资源中没有ANSI版本的字符串,那么它可能返回什么?您不能返回不存在的指针。 VerQueryValueA需要产生一个ANSI字符串,并且它是从内存中提取出资源时由GetFileVersionInfo准备的。
对于您要尝试执行的操作,仅需要从复制的资源中查询VS_FIXEDFILEINFO
。它包含您要查找的版本号,与语言无关,并且不依赖于GetFileVersionInfo()
:
std::string get_version_string()
{
auto hInst = GetModuleHandle(NULL);
auto hResInfo = FindResourceA(hInst, MAKEINTRESOURCE(1), RT_VERSION);
if ( !hResInfo ) return {};
auto dwSize = SizeofResource(hInst, hResInfo);
if ( !dwSize ) return {};
auto hResData = LoadResource(hInst, hResInfo);
char *pRes = static_cast<char *>(LockResource(hResData));
if ( !pRes ) return {};
std::vector<char> ResCopy(pRes, pRes + dwSize);
VS_FIXEDFILEINFO *pvFileInfo;
UINT uiFileInfoLen;
if ( !VerQueryValueA(ResCopy.data(), "\\", reinterpret_cast<void**>(&pvFileInfo), &uiFileInfoLen) )
return "(unknown)"s;
char buf[25];
int len = sprintf(buf, "%hu.%hu.%hu.%hu",
HIWORD(pvFileInfo->dwFileVersionMS),
LOWORD(pvFileInfo->dwFileVersionMS),
HIWORD(pvFileInfo->dwFileVersionLS),
LOWORD(pvFileInfo->dwFileVersionLS)
);
return std::string(buf, len);
}