Windows 7 文件属性“版本”选项卡外壳扩展






4.84/5 (50投票s)
简单解析 VS_VERSIONINFO 版本信息字符串,
引言
令许多人感到沮丧的是,Windows 7 用新的“详细信息”选项卡替换了 Windows 2000 / Windows XP 的原始“版本”文件属性选项卡。

取而代之的是一个名为“详细信息”的新选项卡。

不幸的是,它既不显示与以前相同的所有信息,也不支持复制和粘贴显示的任何信息。
然而,有了这个方便的 VersInfoEx Shell Extension,这种情况就不再是这样了!VersInfoEx Shell Extension 将丢失的版本选项卡功能带回了 Windows 7!

背景
利用 Michael Dunn 在他出色的系列文章《编写 Shell 扩展完全傻瓜指南》(特别是系列文章的第五部分)中提供的信息,我能够快速构建一个相当简单的属性页 Shell 扩展,该扩展可以显示文件的 VS_VERSIONINFO
文件版本资源信息。
该代码使用了一个手工编写的 C++ 类,该类解析所有 VS_VERSIONINFO
文件版本资源信息——包括所有可能存在的 StringTables
的所有版本字符串——并将它们封装到几个变量中,存储在一个简单的类中,以便于程序访问。
然后,Shell 扩展本身会将此类的这些信息简单地显示给用户。
Using the Code
CVersionInfo
类完成了所有繁重的工作,解析 VS_VERSIONINFO
文件版本资源信息。我根据 SDK 中的信息从头开始编写了它。
诚然,CodeProject 上还有其他几篇文章展示了如何解析版本信息,但它们都存在一个或多个方面的不足。大多数文章未能解析所有可用的版本信息字符串,而且在我看来,它们整体的解析逻辑过于复杂和笨拙。
CVersionInfo::Init()
函数中的简单解析逻辑通过提供一个非常简短且简单的算法来解析所有版本 `string`s,从而克服了这两个缺点。
// Point to the VS_VERSIONINFO block passed to us (key = "VS_VERSION_INFO")
BLOCK* pVersionInfo = (BLOCK*) pVI;
// The root VS_VERSIONINFO block's value data is a VS_FIXEDFILEINFO structure
if (pVersionInfo->wValueLength)
memcpy( &m_FFInfo, BlockValue( pVersionInfo ), min( sizeof( m_FFInfo ),
pVersionInfo->wValueLength ) );
// Process all of the root block's child blocks...
BLOCK* pBlock = ChildBlock( pVersionInfo );
BLOCK* pEndVersInfo = EndBlock( pVersionInfo );
for (; pBlock < pEndVersInfo; pBlock = NextBlock( pBlock ))
{
if (_wcsicmp( pBlock->szKeyW, L"VarFileInfo" ) == 0)
{
// "VarFileInfo" child BLOCKs are "Var" BLOCKS...
BLOCK* pVar = (BLOCK*) ChildBlock( pBlock );
BLOCK* pEndVars = EndBlock( pBlock );
for (; pVar < pEndVars; pVar = NextBlock( pVar ))
{
if (_wcsicmp( pVar->szKeyW, L"Translation" ) == 0)
{
DWORD* pLangDword = BlockValue( pVar );
WORD nNumDwords = pVar->wValueLength / sizeof(DWORD);
...(process language/codepage array)...
}
}
}
else if (_wcsicmp( pBlock->szKeyW, L"StringFileInfo" ) == 0)
{
// "StringFileInfo" child BLOCKs are "StringTable" BLOCKS...
BLOCK* pStrTab = (BLOCK*) ChildBlock( pBlock );
BLOCK* pEndStrTab = EndBlock( pBlock );
for (; pStrTab < pEndStrTab; pStrTab = NextBlock( pStrTab ))
{
// "StringTable" child BLOCKs are "String" BLOCKS...
BLOCK* pString = (BLOCK*) ChildBlock( pStrTab );
BLOCK* pEndStrings = EndBlock( pStrTab );
for (; pString < pEndStrings; pString = NextBlock( pString ))
{
CStringW strNameW = pString->szKeyW;
CStringW strValueW = (LPCWSTR) BlockValue( pString );
...(process versinfo String)...
}
}
}
}
这种简洁性来自于使用非常小的“辅助”函数,这些函数正确地调整了传递的 `BLOCK` 结构指针。文件版本信息资源 `BLOCK` 结构如下所示:
struct BLOCK // (always aligned on 32-bit (DWORD) boundary)
{
WORD wLength; // Length of this block (doesn't include padding)
WORD wValueLength; // Value length (if any)
WORD wType; // Value type (0 = binary, 1 = text)
WCHAR szKeyW[]; // Value name (block key) (always NULL terminated)
//WORD padding1[]; // Padding, if any (ALIGNMENT)
//xxxxx Value[]; // Value data, if any (*ALIGNED*)
//WORD padding2[]; // Padding, if any (ALIGNMENT)
//xxxxx Child[]; // Child block(s), if any (*ALIGNED*)
};
每个块总有一个键,但可能有一个值成员,也可能没有,而该值本身可能是一个或多个子块等。
所有块都从 `DWORD` (32位) 对齐边界开始,它们的值数据和子块(可能存在于值数据之外)也是如此。
块长度或值长度字段都不包括块结束和下一个块开始之间,或者键结束和值数据开始之间可能存在的任何填充。
“辅助”函数本身如下:
// Helper functions for navigating through VERSIONINFO data...
BLOCK* RoundUp32 ( BLOCK* p, size_t n )
{ return (BLOCK*) (((ptrdiff_t) p + n + 3) & ~3); }
BLOCK* AlignBlock ( BLOCK* pBlk, BLOCK* p )
{ return RoundUp32( pBlk, ((BYTE*)p - (BYTE*)pBlk) ); }
BLOCK* BlockValue ( BLOCK* pBlk )
{ return AlignBlock( pBlk, (BLOCK*) ((BYTE*)pBlk + sizeof(BLOCK)
+ (( wcslen( pBlk->szKeyW ) + 1) * sizeof(WCHAR))) ); }
BLOCK* EndBlock ( BLOCK* pBlk )
{ return (BLOCK*) ((BYTE*) pBlk + pBlk->wLength); } // (NOTE: must NOT be rounded)
BLOCK* ChildBlock ( BLOCK* pBlk )
{ return AlignBlock( pBlk, (BLOCK*) ((BYTE*) BlockValue( pBlk )
+ pBlk->wValueLength) ); }
BLOCK* NextBlock ( BLOCK* pBlk )
{ return AlignBlock( pBlk, EndBlock( pBlk )); }
利用这些信息(上述函数),如果您愿意,可以轻松地编写自己的“HasChild()
”函数,如下所示:
BOOL HasChild( BLOCK* pBlk ) { return ChildBlock( pBlk ) < EndBlock( pBlk ); }
关注点
得益于 Michael Dunn 的“编写 Shell 扩展完全傻瓜指南——第五部分”,构建和显示版本选项卡本身的实际 Shell 扩展代码非常简单。
它涉及在 `OnInitDialog` 回调中进行一些简单的 `SendDlgItemMessage` 调用,以设置各种编辑控件中的文本,以及在属性页回调中进行一些 `SendMessage` 调用。就是这样!相当简单。
唯一困难的部分是注意潜在的“陷阱”,即需要记住保存动态分配的数据结构(`CVersionInfo` 类对象)的指针,以便在属性页回调处理过程中能够再次检索它(以显示用户点击的 `String` 值)。
- 在用户点击他们选择的字符串值时,能够检索到该字符串值。
- 在对话框最终关闭时删除它,以防止内存泄漏。
正如 Michael 的文章所示,诀窍是将它保存在两个不同的地方:
- 在“
AddPages
”期间的 `PROPSHEETPAGE` 结构的 `lParam` 字段中。 - 通过 `SetWindowLongPtr` 在页面对话框的窗口对象本身中。
这些重要的技术都体现在源文件 *VersInfoExShlExt.cpp* 的 `CVersInfoShlExt::AddPages`、`OnInitDialog`、`PropPageDlgProc` 和 `PropPageCallbackProc` 函数中。
我选择使用 ATL 的 `CSimpleMap` 类,当需要保存每个列表框项中的一些信息以识别该项是哪个资源 `String` 时,也非常方便。我本来可以直接检索列表框项的 `string`,然后将其作为查找键来查找我的 `string` 映射中的相应值,但 ATL 的“简单映射”条目可以通过直接索引直接访问,这使得事情变得简单:我只是保存了映射的索引值!(一个简单的数字整数值)
安装
我不提供安装程序,因为安装 Shell 扩展非常简单。
- 将 DLL 复制到您的 %SystemRoot%\system32 文件夹。
- 打开管理员命令提示符窗口并输入命令:
regsvr32 "C:\Windows\system32\VersInfoEx.dll"
- 注销,然后重新登录,以强制 Shell(explorer.exe)刷新。
请注意,您必须在 regsvr32
命令中输入完整路径,因为这是实际写入注册表的值。
如果您想卸载它,只需“注销”Shell 扩展,方法是输入相同的命令,但指定“/u
”(注销)选项。然后只需从 Windows system32 目录中删除 DLL。
我只在 Windows 7 x64 上测试过此 Shell 扩展,但它应该适用于任何 32 位或 64 位版本的 Windows。
好了,我想就这些了。尽情享受您的 Windows 7 系统上重新出现的“版本”选项卡吧!