Visual Studio C++项目自动化版本号管理:从原理到实战
2026/7/27 4:08:00 网站建设 项目流程

1. 项目概述:为什么C++项目需要一个“版本管家”?

干了这么多年C++开发,尤其是在Visual Studio这个老伙计的陪伴下,我经手过不少从零到一,再到持续迭代好几年的项目。不知道你有没有遇到过这种场景:测试同事跑过来说,“你昨天给我的那个Debug版本,和今天这个,功能上好像有点不一样?” 或者,线上突然报了个诡异的Bug,你翻遍SVN或Git的提交记录,想定位到底是哪个版本引入的问题,结果发现,所有的可执行文件都叫MyApp.exe,唯一的区别可能就是文件日期——这简直是大海捞针。

这就是我们今天要聊的核心:给Visual Studio下的C++项目,系统化地增加和管理版本号。这听起来像是个“锦上添花”的小功能,但在实际的团队协作、持续集成、问题排查和发布管理中,它扮演着“定海神针”的角色。一个清晰、自动、可追溯的版本号,就像是给每个构建产物打上了一个独一无二的身份证,上面写着:我是谁(产品名),我处于生命周期的哪个阶段(主版本.次版本),我是第几次构建(修订号),甚至是在哪个时间点、基于哪次代码提交诞生的(构建号/提交哈希)。

更关键的是“模块化管理”。现代软件,尤其是C++项目,动辄几十上百个模块(DLL、静态库),每个模块都有自己的迭代节奏。如果还用手动修改rc文件或者头文件里的#define,不仅容易出错,版本同步更是噩梦。我们需要的是一个中心化的、可配置的、能自动渗透到每个相关模块的版本管理方案。

所以,这个“项目”的目标很明确:在Visual Studio环境下,建立一套自动化、可配置、支持模块化同步的C++项目版本号管理机制。接下来,我会把我趟过的路、踩过的坑,以及最终稳定运行的方案,毫无保留地分享给你。

2. 版本号设计哲学与标准选择

在动手写第一行代码或改第一个配置文件之前,我们必须先统一“语言”,也就是版本号的格式和含义。没有规矩,不成方圆。

2.1 常见版本号规范解析

市面上主流的版本号规范有好几种,我们需要根据项目性质来选择:

  1. 语义化版本(SemVer):格式为主版本号.次版本号.修订号,例如2.1.15。这是目前开源世界和商业软件最推崇的规范。

    • 主版本号:做了不兼容的 API 修改。
    • 次版本号:向下兼容的功能性新增。
    • 修订号:向下兼容的问题修正。
    • 优点:含义清晰,能直接传达版本兼容性信息,对依赖管理极其友好。
    • 适用场景:提供公共API的库(如OpenSSL、Boost)、有明确版本发布周期的应用程序。
  2. 微软风格版本:通常表现为四段式,如文件版本:2.1.15.1024产品版本:2.1.15。在Windows资源文件(.rc)和文件属性中常见。

    • 前三位(2.1.15)通常对应语义化版本。
    • 第四位(1024)是构建号(Build Number),由构建系统自动生成,常用于区分每日构建或持续集成产生的不同版本。
    • 优点:与Windows生态系统无缝集成,能清晰区分“产品版本”和“文件版本”。
  3. 日期版本号:如2024.0510.1,表示2024年5月10日的第1次构建。

    • 优点:一眼就能看出构建时间,对需要频繁每日构建(Nightly Build)或内部测试的项目非常直观。
    • 缺点:无法体现兼容性和功能变化。

对于大多数Visual Studio C++项目,我强烈推荐采用“语义化版本 + 自动构建号”的组合策略。即:

  • 产品版本(Product Version)主版本.次版本.修订号(如1.2.0),用于对外发布和标识功能阶段,手动或根据Git Tag更新。
  • 文件版本(File Version)主版本.次版本.修订号.构建号(如1.2.0.1024),用于唯一标识每一个具体的构建产出,构建号由CI/CD系统自动递增。

2.2 版本信息注入Windows资源的必要性

在Windows平台上,版本信息需要写入到可执行文件(EXE)或动态库(DLL)的资源段中。这样,在文件管理器里右键点击文件 -> “属性” -> “详细信息”选项卡,就能看到规整的版本信息。这不仅是为了好看,更是为了:

  • 安装程序识别:许多安装工具(如MSI)依赖文件版本来决定是否覆盖。
  • 调试与支持:用户报告问题时,可以轻松获取完整的版本信息。
  • 系统管理:管理员可以通过PowerShell等工具批量查询软件版本。

在Visual Studio C++项目中,这部分信息通常定义在一个.rc(资源脚本)文件里,其中包含一个VS_VERSION_INFO资源块。我们的自动化方案,最终就是要动态生成或更新这个资源块中的相关字段。

注意:版本资源中的FILEVERSIONPRODUCTVERSION是4个由逗号分隔的整数,例如1,2,0,1024。而在字符串表中显示的“文件版本”和“产品版本”,通常是点号分隔的字符串,如1.2.0.1024。两者需要保持一致,但格式不同,这是新手常踩的坑。

3. 核心方案选型:手动、半自动与全自动

根据项目规模和团队流程,版本号管理可以分为几个层次。

3.1 方案一:手动维护(不推荐,但需了解)

这是最原始的方式,直接编辑项目的resource.h.rc文件。

  • resource.h中定义宏:
    #define VER_FILEVERSION 1,2,0,1024 #define VER_FILEVERSION_STR "1.2.0.1024\0" #define VER_PRODUCTVERSION 1,2,0,0 #define VER_PRODUCTVERSION_STR "1.2.0\0"
  • .rc文件中引用这些宏:
    #include "resource.h" // ... VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION PRODUCTVERSION VER_PRODUCTVERSION // ... VALUE "FileVersion", VER_FILEVERSION_STR VALUE "ProductVersion", VER_PRODUCTVERSION_STR
  • 缺点:极易遗忘,多人协作时冲突频繁,无法自动递增构建号,与源码版本管理脱节。

3.2 方案二:预生成头文件与构建后事件(推荐中小项目)

这是性价比很高的半自动化方案,利用Visual Studio的“生成前事件”和“生成后事件”。

  1. 创建一个版本模板文件,如version_template.h.in,内容类似resource.h,但版本号部分使用占位符:
    #pragma once #define VER_MAJOR @PROJECT_VERSION_MAJOR@ #define VER_MINOR @PROJECT_VERSION_MINOR@ #define VER_PATCH @PROJECT_VERSION_PATCH@ #define VER_BUILD @BUILD_NUMBER@ #define VER_FILEVERSION VER_MAJOR, VER_MINOR, VER_PATCH, VER_BUILD #define VER_FILEVERSION_STR _T("@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.@BUILD_NUMBER@")
  2. 编写一个简单的脚本(Python/PowerShell/Batch),在“生成前事件”中执行。这个脚本做三件事:
    • 从某个地方(如一个简单的version.txt文件、Git标签、环境变量)读取主版本号、次版本号。
    • 自动生成或读取一个递增的构建号(可以基于日期时间戳,或一个持久化的计数器文件)。
    • 用读取到的值替换模板中的占位符,生成最终的version.h文件。
  3. 在项目的rc文件和所有需要版本号的源码中,包含这个自动生成的version.h
  4. 在“生成后事件”中,可以可选地将本次构建的版本信息写入日志或上传到服务器。
  • 优点:实现简单,无需额外工具,能与Visual Studio项目属性紧密集成。
  • 缺点:构建号的生成逻辑相对简单,跨项目同步版本号需要额外设计(比如让所有项目都读取同一个中央version.txt)。

3.3 方案三:使用CMake等构建系统(推荐大型/跨平台项目)

如果你的项目已经开始使用或考虑使用CMake,那么管理版本号会变得非常优雅。CMake本身提供了一个project()命令,可以声明版本号,并且能自动生成包含版本信息的头文件。

  1. 在顶层CMakeLists.txt中:
    cmake_minimum_required(VERSION 3.10) project(MyAwesomeProject VERSION 1.2.0 LANGUAGES CXX) # 你可以通过 ${PROJECT_VERSION_MAJOR} 等变量访问版本各部分
  2. 使用configure_file()命令处理模板文件,原理与方案二类似,但由CMake在配置阶段完成,更集成化。
    # 假设有个 version.h.in 模板 configure_file(version.h.in generated/version.h @ONLY) include_directories(${CMAKE_CURRENT_BINARY_DIR}/generated)
  3. 对于构建号,CMake没有内置机制,但可以通过自定义脚本、读取Git提交信息(如git describe --tags --always)或环境变量来获取,并传递给configure_file
  4. 在Windows上,CMake可以生成包含正确版本资源的.rc文件。对于更复杂的需求,可以使用FindRC模块或直接编写.rc.in模板进行配置。
  • 优点:与构建系统深度集成,跨平台支持好,是现代C++项目的趋势。
  • 缺点:需要引入或迁移到CMake,有一定学习成本。

3.4 方案四:集成到CI/CD流水线(企业级最佳实践)

这是最彻底、最自动化的方案。版本号的“所有权”完全交给持续集成/持续部署系统(如Jenkins, GitLab CI, Azure DevOps, GitHub Actions)。

  1. 版本号来源:主版本号、次版本号可以固化在CI配置文件中,或由Git标签(Tag)驱动。例如,打上v2.1.0的标签即触发发布构建,CI系统自动解析此标签作为产品版本。
  2. 构建号生成:CI系统通常提供唯一且递增的构建ID(如BUILD_IDRUN_NUMBER),完美用作构建号。
  3. 注入过程:CI流水线在编译前,通过脚本将版本号写入项目目录下的一个配置文件(如version.props属性表),或直接作为编译预定义宏(/D命令行参数)传递给MSBuild。
  4. 统一输出:所有解决方案(Solution)中的项目,都通过导入统一的version.props属性表来获取版本号,实现模块间的绝对同步。
  • 优点:全自动,可追溯性强(版本号与CI构建记录绑定),非常适合敏捷开发和频繁发布。
  • 缺点:依赖于CI/CD基础设施,本地开发构建时可能需要一个后备的版本号生成机制。

实操心得:对于大多数团队,我建议从方案二开始,它简单有效,能立刻解决手动维护的痛点。当项目逐渐复杂,特别是需要管理多个相互依赖的模块时,可以平滑过渡到方案四,将版本控制作为DevOps流程的一环。方案三则是技术栈升级时的自然选择。

4. 实战:基于属性表与预生成事件的模块化管理方案

下面,我将详细拆解一个结合了方案二和方案四思想的、在Visual Studio 2019/2022中经过实战检验的方案。这个方案的核心思想是:“一次定义,处处使用”,通过Visual Studio的“属性表”实现版本号的集中管理和模块化共享。

4.1 创建中心化的版本属性表

  1. 新建属性表:在解决方案资源管理器中,右键点击你的解决方案 -> 添加 -> 新建项目 -> 选择“属性表”,命名为Version.props。我习惯把它放在解决方案根目录下的BuildProperties文件夹里,方便管理。
  2. 编辑属性表:双击打开Version.props。我们需要在“通用属性”->“用户宏”中定义版本变量。
    • 点击“添加宏”。
    • 名称:VersionMajor,值:1
    • 名称:VersionMinor,值:2
    • 名称:VersionPatch,值:0
    • 名称:VersionBuild,值:$([System.DateTime]::Now.ToString("yyyyMMdd"))(这是一个使用MSBuild内联任务的示例,生成20240510格式的日期作为构建号。对于更复杂的逻辑,建议使用外部脚本)
  3. 定义预处理器宏:在“C/C++” -> “预处理器” -> “预处理器定义”中,添加基于用户宏的预处理器定义。这里很关键,因为.rc文件编译时也需要这些宏。
    VERSION_MAJOR=$(VersionMajor) VERSION_MINOR=$(VersionMinor) VERSION_PATCH=$(VersionPatch) VERSION_BUILD=$(VersionBuild) FILE_VERSION=\"$(VersionMajor).$(VersionMinor).$(VersionPatch).$(VersionBuild)\" PRODUCT_VERSION=\"$(VersionMajor).$(VersionMinor).$(VersionPatch)\"

    注意:预处理器定义中的字符串需要用反斜杠转义引号,即\"

4.2 改造资源文件(.rc)以使用动态宏

现在,我们需要修改项目的.rc文件,使其使用我们定义的预处理器宏,而不是硬编码的数字。

  1. .rc文件开头,移除或注释掉对固定resource.h中版本宏的引用。改为直接使用预处理器宏。
  2. 修改VS_VERSION_INFO块:
    // 旧版本(硬编码) // FILEVERSION 1,0,0,1 // PRODUCTVERSION 1,0,0,1 // ... // VALUE "FileVersion", "1.0.0.1" // VALUE "ProductVersion", "1.0.0" // 新版本(使用宏) FILEVERSION VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, VERSION_BUILD PRODUCTVERSION VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, 0 // 产品版本通常不需要构建号 // ... BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" // 英语(美国)代码页 BEGIN VALUE "FileVersion", FILE_VERSION VALUE "ProductVersion", PRODUCT_VERSION VALUE "InternalName", "YourApp.exe" VALUE "OriginalFilename", "YourApp.exe" // ... 其他字符串信息 END END // ... VarFileInfo 块 END
    关键点FILEVERSIONPRODUCTVERSION后面跟的是由逗号分隔的4个数字,所以我们传入了VERSION_MAJOR等宏。而字符串表中的FileVersionProductVersion值,我们直接使用了FILE_VERSIONPRODUCT_VERSION这两个字符串宏。

4.3 为项目关联属性表并实现模块同步

  1. 关联属性表:打开每个需要统一版本号的C++项目属性页。在“通用属性” -> “框架和引用”下,点击“添加新引用”,可以引用其他项目。但这里我们需要的是属性表。在“通用属性” -> “属性管理器”视图中(如果没看到,在“视图”菜单中打开),为每个项目的每个配置(Debug, Release等)右键 -> “添加现有属性表”,选择我们创建的Version.props
  2. 模块化同步的奥秘:至此,所有引用了同一个Version.props文件的项目,在编译时都会使用其中定义的VersionMajor等用户宏。当你需要更新版本号时,只需修改这一个Version.props文件,然后重新生成解决方案,所有项目的版本号都会自动同步更新。这完美解决了多模块项目的版本一致性问题。
  3. 在代码中访问版本号:有时我们需要在程序运行时读取自身的版本号。可以创建一个公共头文件,如app_version.h,同样利用属性表中的预处理器宏:
    // app_version.h #pragma once #include <string> #include <sstream> namespace AppVersion { constexpr int MAJOR = VERSION_MAJOR; constexpr int MINOR = VERSION_MINOR; constexpr int PATCH = VERSION_PATCH; constexpr int BUILD = VERSION_BUILD; inline std::string GetVersionString() { std::ostringstream oss; oss << MAJOR << "." << MINOR << "." << PATCH << "." << BUILD; return oss.str(); } inline std::string GetProductVersionString() { std::ostringstream oss; oss << MAJOR << "." << MINOR << "." << PATCH; return oss.str(); } }
    在任何源文件中包含此头文件,即可使用AppVersion::GetVersionString()获取版本字符串。

4.4 集成自动化构建号生成脚本

上面的例子用内联任务生成了日期构建号。但对于需要严格递增的构建号,我们需要一个外部脚本。这里以Python脚本为例,结合“生成前事件”:

  1. 编写版本脚本update_build_number.py:
    #!/usr/bin/env python3 import os import sys import re def read_version_props(filepath): major = minor = patch = 0 with open(filepath, 'r') as f: content = f.read() # 简单解析XML,查找 VersionMajor 等值。实际可使用xml.etree.ElementTree major_match = re.search(r'<VersionMajor>(\d+)</VersionMajor>', content) minor_match = re.search(r'<VersionMinor>(\d+)</VersionMinor>', content) patch_match = re.search(r'<VersionPatch>(\d+)</VersionPatch>', content) if major_match: major = int(major_match.group(1)) if minor_match: minor = int(minor_match.group(1)) if patch_match: patch = int(patch_match.group(1)) return major, minor, patch def update_build_number(props_file, build_num_file='build_number.txt'): # 读取或初始化构建号 if os.path.exists(build_num_file): with open(build_num_file, 'r') as f: build_num = int(f.read().strip()) + 1 else: build_num = 1 # 或从CI环境变量获取 # 更新构建号文件 with open(build_num_file, 'w') as f: f.write(str(build_num)) # 读取属性表模板,替换构建号占位符,生成最终属性表 # 这里假设有一个 Version.props.in 模板,里面有 @BUILD_NUMBER@ 占位符 with open('Version.props.in', 'r') as f: template = f.read() final_content = template.replace('@BUILD_NUMBER@', str(build_num)) with open(props_file, 'w') as f: f.write(final_content) print(f"Updated build number to: {build_num}") return build_num if __name__ == '__main__': update_build_number('Version.props')
  2. 配置生成前事件:在Version.props属性表(或主项目的属性页)中,进入“生成事件” -> “预生成事件”。
    • 命令行输入:python $(SolutionDir)scripts\update_build_number.py "$(ProjectDir)Version.props"(请根据你的脚本实际路径调整)
    • 这样,每次编译前,脚本都会自动递增构建号并更新Version.props

重要提示:将build_number.txt文件加入.gitignore,避免构建号被提交到代码库引起冲突。构建号应该只在构建机器上持久化。

5. 高级主题:与CI/CD和安装项目集成

当你的项目需要走向自动化部署时,版本号管理需要与更广阔的流程对接。

5.1 在Azure DevOps / GitHub Actions中驱动版本

在CI流水线中,版本号通常由管道变量或Git标签决定。

  1. 使用Git标签作为产品版本:在CI脚本中,可以运行git describe --tags --always --match "v[0-9]*"来获取最近的标签,并解析出主、次、修订号。如果没有标签,则使用默认值或提交哈希。
  2. 使用流水线运行号作为构建号:Azure DevOps中有$(Build.BuildId),GitHub Actions中有${{ github.run_number }},这些都是天然递增、全局唯一的完美构建号。
  3. 注入到MSBuild:在CI的构建任务中,将这些版本号作为MSBuild参数传递:
    msbuild MySolution.sln /p:Configuration=Release /p:Platform=x64 /p:VersionMajor=2 /p:VersionMinor=1 /p:VersionPatch=0 /p:VersionBuild=$(Build.BuildId)
  4. 修改属性表以接受外部参数:我们需要改造Version.props,使其优先使用外部传入的参数。这可以通过在属性表中使用条件判断来实现。但更简单的方式是,在CI流水线中,用一个脚本根据环境变量动态生成最终的Version.props文件,替换掉项目中的模板。

5.2 让安装项目(如Setup Project、WiX)也使用统一版本

如果你的解决方案里包含Visual Studio安装项目(Visual Studio Installer Projects)或使用WiX工具集,确保安装包版本与主程序一致至关重要。

  1. 对于VS安装项目:安装项目的版本属性是独立的。你可以在安装项目的“属性”窗口中设置版本。为了实现同步,一个办法是写一个“生成后事件”,从主程序输出的EXE/DLL文件中读取版本信息(例如使用System.Diagnostics.FileVersionInfo.GetVersionInfo写一个小工具),然后去修改安装项目文件(.vdproj)中的版本号字段。这个过程比较繁琐,也是很多人放弃VS安装项目的原因之一。
  2. 对于WiX(Windows Installer XML):WiX是高度可编程的。你可以在WiX项目(.wixproj)中,通过MSBuild任务读取主项目的版本号,并传递给WiX的预处理器变量(PreprocessorVariable)或通过Harvest(Heat)工具自动获取文件的版本。
    • .wixproj文件中,可以添加一个Target,在Build之前执行,调用一个脚本获取版本号,并设置为MSBuild属性。
    • .wxs文件中,使用<?define ProductVersion="$(var.Version)"?>来引用这个变量。
    • 这样,整个解决方案的版本号源头依然是那个Version.props或CI变量,通过MSBuild的依赖关系自动传递到WiX,实现全局统一。

5.3 版本号与调试符号(PDB)文件

确保版本信息也嵌入到PDB文件中,对于崩溃转储(Dump)分析非常有用。幸运的是,当你在资源文件中正确设置了版本信息,并且使用/DEBUG选项编译时,Visual Studio的链接器会自动将版本信息关联到生成的PDB中。使用SymChk或调试器查看PDB详情时,就能看到对应的版本。

6. 常见问题与排查技巧实录

即使方案设计得再完美,实操中总会遇到各种“坑”。下面是我总结的一些典型问题及其解决方法。

6.1 资源编译错误:RC2104: undefined keyword or key name

  • 问题描述:编译时,资源编译器(rc.exe)报错,提示预处理器宏未定义。
  • 根本原因:资源编译器在解析.rc文件时,没有获得你在项目属性中定义的预处理器宏。项目属性中C/C++的预处理器定义,默认只传递给C++编译器(cl.exe),不自动传递给资源编译器。
  • 解决方案
    1. 打开项目属性 -> “资源” -> “常规”。
    2. 在“附加包含目录”中,确保包含了定义宏的头文件所在目录(如果宏定义在头文件里)。
    3. 最关键的一步:在“资源” -> “命令行”的“附加选项”中,手动添加定义宏的参数:
      /D "VERSION_MAJOR=$(VersionMajor)" /D "VERSION_MINOR=$(VersionMinor)" /D "VERSION_PATCH=$(VersionPatch)" /D "VERSION_BUILD=$(VersionBuild)"
      这样,资源编译器就能接收到和C++编译器一样的宏定义了。

6.2 文件属性中版本信息显示为“0.0.0.0”或空白

  • 问题描述:程序编译成功,但右键查看EXE属性时,版本信息页是空的或全是0。
  • 排查步骤
    1. 检查.rc文件是否被编译:在解决方案资源管理器中,确保.rc文件存在于项目中,并且其“项类型”为“资源编译器”。有时文件被意外排除在生成之外。
    2. 检查宏展开是否正确:使用Visual Studio的“预处理器”视图查看.rc文件展开后的结果。右键点击.rc文件 -> “属性” -> “常规” -> “从生成中排除”选择“否”,然后编译。在输出目录找到中间文件(通常在项目名.dir\Debug\Rxxxxxxx文件夹里的.res文件对应的.rc展开文件),打开查看FILEVERSION等关键字后面是不是具体的数字。
    3. 检查数字格式:确保FILEVERSION后面是4个由逗号分隔的整数,如1,2,0,1024,而不是1.2.0.1024。字符串表里的VALUE "FileVersion", "1.2.0.1024"才是点号分隔。
    4. 检查字符集:如果你的项目使用Unicode字符集,确保字符串常量前加了_T()L前缀,如_T("1.2.0.1024"),否则可能导致乱码或显示不全。

6.3 多项目解决方案中,版本号更新后部分项目未生效

  • 问题描述:修改了Version.props后,重新生成解决方案,但有些DLL的版本号还是旧的。
  • 原因与解决
    • 项目未引用属性表:在“属性管理器”中仔细检查,确保所有项目在所有配置(Debug|x64, Release|x86等)下都添加了Version.props
    • 属性表未被重新加载:Visual Studio有时会缓存属性表。尝试“清理解决方案”,然后“重新生成解决方案”。
    • 依赖项生成顺序:如果A项目依赖B项目,且B项目生成了lib/dll被A项目链接,确保B项目先于A项目重新编译。检查解决方案的“项目依赖项”设置。
    • 中间文件缓存:手动删除所有项目的中间输出目录(通常是DebugReleasex64等文件夹)和解决方案的*.suo*.vcxproj.user文件,然后重新打开解决方案并生成。

6.4 构建号在每次本地编译时都递增,导致版本号“污染”仓库

  • 问题描述:按照上述预生成事件脚本,每次按F5调试,构建号都会+1,导致可执行文件版本不停变化,且构建号文件可能被误提交。
  • 解决方案
    • 区分本地构建与CI构建:在预生成脚本中,检查是否存在CI环境变量(如TF_BUILDfor Azure DevOps,CIfor GitHub Actions)。如果存在,则使用CI提供的构建号;如果不存在,则可以使用一个固定的本地开发构建号(如9999),或者基于日期时间生成一个不会提交的构建号。
    • 使用.gitignore:务必把存储自动生成构建号的文件(如build_number.txt、自动生成的version.h)加入到.gitignore中。
    • 将版本生成移至CI阶段:最干净的做法是,本地开发时版本号固定或使用占位符,只在CI服务器上执行版本号替换和递增的步骤。这需要将版本模板文件(如version.h.in)纳入版本控制,而将生成最终文件的步骤作为CI流水线的一部分。

6.5 如何从程序中读取自身版本号

除了在代码中直接使用预处理器宏,有时需要在运行时动态读取。可以使用Windows APIGetFileVersionInfoVerQueryValue

#include <windows.h> #include <string> std::string GetModuleVersionStr(HMODULE hModule = nullptr) { std::string version; char modulePath[MAX_PATH]; GetModuleFileNameA(hModule, modulePath, MAX_PATH); DWORD dummyHandle; DWORD infoSize = GetFileVersionInfoSizeA(modulePath, &dummyHandle); if (infoSize) { std::vector<BYTE> buffer(infoSize); if (GetFileVersionInfoA(modulePath, 0, infoSize, buffer.data())) { VS_FIXEDFILEINFO* pFileInfo = nullptr; UINT len = 0; if (VerQueryValue(buffer.data(), "\\", (LPVOID*)&pFileInfo, &len)) { version = std::to_string(HIWORD(pFileInfo->dwFileVersionMS)) + "." + std::to_string(LOWORD(pFileInfo->dwFileVersionMS)) + "." + std::to_string(HIWORD(pFileInfo->dwFileVersionLS)) + "." + std::to_string(LOWORD(pFileInfo->dwFileVersionLS)); } } } return version; }

这个函数可以读取指定模块(EXE或DLL)的文件版本,非常适合用于日志输出或关于对话框。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询