65.9K
CodeProject 正在变化。 阅读更多。
Home

Windows 的简单版本资源工具

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (65投票s)

2009年6月9日

CPOL

9分钟阅读

viewsIcon

390823

downloadIcon

28569

无需资源编译器即可在可执行文件上创建版本信息的实用程序

引言

本文介绍了一个我们为维护原生控制台应用程序、DLL、设备驱动程序和其他非 GUI 程序组件而制作的小工具。

开发人员经常需要为这些组件添加资源脚本 (.rc),仅仅是为了版本信息资源。版本资源中的文本字符串经常需要更改,并且版本号需要保持同步。所有这些都很乏味且容易出错——尤其是在如今,对于 Visual C# 应用程序,版本信息资源是自动生成的。

该实用程序允许在需要时添加或修改版本信息——在构建组件的过程中,或之后任何时候。构建过程可以简化,因为对多个产品、OEM 版本通用的、仅在版本信息上有所定制的模块,可以只构建一次。

更重要的是,对版本信息和其他一些资源数据的修补可以在无需访问代码和构建环境的情况下进行,因此这项任务可以延迟到构建后的发布和打包阶段。例如,市场营销或现场工程人员可以轻松编辑在 Windows 资源管理器中可见的信息和某些资源(如清单),而无需请求开发人员的帮助。这可以节省时间并消除潜在的错误。

下面的示例用一个简单的命令修改了 foo.dll 的文件版本信息

verpatch foo.dll "1.2.3.4 my special build"

源代码中包含的示例脚本展示了如何一次性设置多个文件的版本信息,以及如何指定所有版本信息元素。

由于该工具以源代码形式提供,您可以轻松地根据您产品的特定要求和构建过程对其进行调整。

代码还演示了使用 Win32 API 进行原生资源和 PE 映像操作。

VerPatch/ver-winxp.png

VerPatch/ver-vista.png

背景

版本信息资源包含在查看程序文件属性时 Windows 资源管理器中可见的信息。

如上图所示,WinXP 的资源管理器显示了版本信息中的所有字符串;在较新版本的 Windows 中,同一文件的显示元素较少。但是,版本资源中的所有数据仍然可以通过 Win32 API(GetFileVersionInfo, GetFileVersionInfoEx, VerQueryValue)访问。

为原生模块提供此数据的传统方法是将原生资源脚本(.rc 文件)添加到项目中,在 .rc 文件中添加 VS_VERSION_INFO 资源,使用 Resource Compiler 编译它,然后链接到二进制文件。此方法需要提供版本信息的所有细节:文件版本号、文件的最终名称、产品名称和版本等——这些可能在构建时还未可知。一个 .RC 文件通常需要复杂的 C 宏和额外的包含文件。

无论如何,手动编写版本资源不是开发者花费时间的最佳方式,而且可以避免。

版本资源数据包含一个“二进制”数据结构,以及一个文本字符串对(键和值)的数组。换句话说,它是 .NET 和 XML 时代之前的某种清单。

某些 .NET 编译器生成的可执行文件具有版本信息属性 AssemblyVersion。这是一个 4 部分的数字,类似于文件版本和产品版本,但它不会复制到版本资源中的二进制部分,并且可能与实际的程序集版本不同,因为 .NET 工具将其视为这样。

然而,我们找不到版本资源中字符串属性数据的正式模式。其中一些元素是“众所周知的”(例如 Visual Studio 创建的,并且 Windows 资源管理器能够识别的)。

版本信息资源的一个臭名昭著的特点是,两个字符串属性 FileVersion ProductVersion 应该是二进制部分中相同字段的文本表示。实际上,它们可能不同,导致混淆。

在 Vista 和 Windows 7 中,Windows 资源管理器仅显示这些属性中的几个;其他对最终用户不可见。显示的**文件版本号**是从版本结构二进制部分获取的。Visual Studio 创建的 Comments、Private Build、Special Build 属性不会显示。如果这些字符串中的信息应该对最终用户可见,请考虑将其移至仍然可见的元素(文件描述、产品名称)。

您可以修改此工具,以便自动重新格式化程序文件的版本信息,而无需重新构建它们,从而使版本信息在最新的 Windows OS 上以最佳方式显示。

版本信息可能取决于文件本身的内容,或附加到文件的各种清单,或其调试信息(.pdb 文件)——但我们将此留给读者作为练习。 

已知一些安装程序、自解压存档和其他应用程序会在可执行文件末尾附加额外数据。这种数据不是资源,而是简单地附加到 PE 文件,例如通过命令 copy /b file.exe + data file2.exe
程序会检测到这种额外数据,将其保存,并在修改资源后再次附加。但是,数据恢复的方式可能与所有这些应用程序都不兼容。请验证包含额外数据的可执行文件在修改后是否正常工作。

用于保存额外数据的代码(pExtras.c,h)由读者 Rob from UK 贡献,用于由 BBC Basic (BB4W) 创建的可执行文件。

请注意,任何对 PE 文件的修改都会破坏其校验和或其他完整性封装,例如数字签名。该工具将自动修复校验和(这是内核驱动程序和受保护 DLL 所必需的),但不会修复签名。它应该是在对应用程序文件进行签名和构建部署包之前的最后一步。

Using the Code

代码是以 Visual C++ 2008(或 Express 版)项目的形式提供的。

我们不提供预编译的可执行文件,因为它很容易从源代码构建。您只需编译该实用程序并运行——它相当有用,即使存在下面提到的限制。
命令行选项和使用说明在源代码中的 readme.txt 文件中进行了描述。

该工具“按原样”支持两种主要的使用场景:

  • 文件根本没有版本资源;创建一个新的版本资源。
  • 文件已经有版本资源;只修改其中的某些部分,其余部分保持不变。

在第一种情况下,所有版本信息细节都可以作为命令行参数提供,缺失的细节将由默认值填充。

在第二种情况下,版本资源在构建时附加到二进制文件。某些版本信息细节(文件版本、特殊构建描述)稍后使用此工具设置。

由于完整版本信息的命令行可能太长,最好从批处理文件或您喜欢的脚本语言运行 verpatch。脚本可以包含所有版本细节,或者从其他地方获取它们,例如 此处所述。

rem Example 2: run verpatch to create version info for foodll.dll  

set VERSION="1.2.3.4 (%date%)"
set FILEDESCR=/s desc "sample foodll description"
set BUILDINFO=/s pb "Built by %USERNAME%"
set COMPINFO=/s company "sample company" /s (c) "(c) Sample copyleft 2009"
set PRODINFO=/s product "sample product" /pv "1.0.22.33"

verpatch /va foodll.dll %VERSION% %FILEDESCR% %COMPINFO% %PRODINFO% %BUILDINFO%

当然,使用脚本,您可以一次更新多个文件。

下面是 verpatch 使用上述选项创建的“经典”RC 版本资源源代码(要生成此输出,请再次使用开关 /vo /xi 在示例 DLL 文件上运行 verpatch)。它甚至可以与 RC 编译。

1       VERSIONINFO
FILEVERSION     1,2,3,4
PRODUCTVERSION  1,0,22,33
FILEFLAGSMASK   0X3FL
FILEFLAGS       0L
FILEOS          0X40004L
FILETYPE        0X2
FILESUBTYPE     0
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904B0"
        BEGIN
            VALUE "FileVersion", "1.2.3.4 (10-Jun-2009)"
            VALUE "ProductVersion", "1.0.22.33"
            VALUE "OriginalFilename", "foodll.dll"
            VALUE "InternalName", "foodll.dll"
            VALUE "FileDescription", "sample foodll description"
            VALUE "CompanyName", "sample company"
            VALUE "LegalCopyright", "(c) Sample copyleft 2009"
            VALUE "ProductName", "sample product"
            VALUE "PrivateBuild", "Built by username"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
            VALUE "Translation", 0x0409, 0x04B0
    END
END

该程序是用非常简单的 C++ 编写的。它可以使用 Visual C++ 2005 或 2008 Express 编译,不依赖于 MFC、ATL 或其他任何东西。

关注点

UpdateResource API 允许在不重新编译或重新链接的情况下添加或替换程序文件的原生资源。这就是我们用来完成任务的方法。由于 VS_VERSION 结构格式不太可能改变,并且在 MSDN 中有记录,我们自己解析这个结构,不依赖于 Resource compiler。

该程序确保文件和产品版本号在版本结构的二进制部分和字符串中是相同的。您只需要指定一次数字。

除了导入和导出版本资源之外,其他值得一提的功能还有:

  • 向可执行文件添加资源文件。这在很多方面都很有用:添加或替换 XML 清单,或使用应用程序特定的原始二进制资源。例如,此程序的帮助文本就是一个自定义资源,用户可以使用程序本身对其进行更新。
  • 使用 Win32 imagehlp API 来修复文件校验和
  • 删除调试信息中 .pdb 路径的选项。我们看到有人要求删除此路径,因为他们认为它是敏感信息。 Wink | <img src= " src="https://codeproject.org.cn/script/Forums/Images/smiley_wink.gif" complete="true" />

与语义版本化规范的兼容性

此实用程序进行了一些调整以支持语义版本化(http://semver.org),仅仅是因为它似乎很有趣。根据此规范,版本号有三个部分(而不是四个),以及可选的字符串后缀,用“ - ”或“ + ”字符分隔(空格不是有效分隔符)。示例: 1.2.3-some.thing.1234

Verpatch 接受此格式的文件和产品版本,如果指定了新开关 /high。然后,版本号的三个部分将被正确解释,并且版本字符串将被保留在版本资源的字符串部分(但是,用户不会在 Windows 资源管理器中看到此文本,如上所述)。如果未指定 /high,verpatch 的行为将与 v.1.0.9 之前相同,以保持兼容性。请参阅 readme 文件以获取语法详细信息。

此版本的限制

最严重的限制是版本解析和生成代码缺乏对语言的支持,但这应该很容易修复。我没有这样做,因为我的项目类型不需要。

旧的(非 Unicode)或奇怪格式的版本资源未被正确处理,可能会导致解析器出错。

请注意,酷炫的 C++ 编码风格、字符串或内存管理不是本文的主题。
我们希望将此实用程序移植到 C#,以便更容易地与现代 .NET 构建工具、Powershell 等集成。

历史

  • 修订版 10 (1.0.10): 2012 年 8 月
  • 修订版 9 (1.0.9): 2011 年 11 月
  • 修订版 6:检测并保存附加到可执行文件的额外数据(代码由读者提供)
  • 修订版 3:修复了处理 LN 版本资源、.NET 可执行文件的代码;文章文本已编辑
  • 修订版 2:代码的一些改进,添加了示例脚本,文本已编辑
  • 文章和发布代码的第一个版本:2009 年 5 月
© . All rights reserved.