Qt开源工具:批量查看DLL和EXE版本信息,支持拖拽解析

发布时间:2026/10/14 12:14:09
Qt开源工具:批量查看DLL和EXE版本信息,支持拖拽解析 1. 从一个实际需求说起为什么我要做这个工具做Windows桌面开发的朋友大概率都遇到过这样的场景手头拿到一个编译好的动态库或者可执行文件想确认它到底是哪个版本、属于哪个模块、编译时间是什么时候结果只能右键属性一个一个翻或者用一些笨重的工具去查。更麻烦的是当项目里依赖了几十个DLL需要批量核对版本号是否匹配时手动操作简直是灾难。我自己就踩过这个坑。之前维护一个跨模块的桌面应用主程序依赖了十几个自研DLL和若干第三方库。某次发版后用户反馈功能异常排查了半天才发现是某个DLL版本没更新到位新旧版本混用导致接口不兼容。当时就想要是有一个轻量工具能一次性把所有DLL的版本信息列出来几秒钟就能定位问题何必浪费那两小时。于是就有了这个基于Qt的开源小项目——查看DLL和EXE版本信息的工具。它的核心功能很纯粹拖入文件或选择目录自动解析PE文件中的版本资源把文件版本、产品版本、公司名称、文件描述、版权信息、编译时间等关键字段以表格形式展示出来支持批量处理和导出。这个项目适合谁如果你是有一定C和Qt基础的开发者想学习Windows PE文件解析、Qt界面开发、跨平台构建这个项目是很好的练手素材。如果你只是想要一个现成的工具来查版本信息直接编译使用也完全没问题。下面我会从设计思路、核心技术点、实操步骤到踩坑经验完整拆一遍。2. 整体设计思路与方案选型2.1 为什么选Qt而不是MFC或纯Win32做Windows工具最直接的选择是MFC或者纯Win32 API。但这两个方案的问题很明显MFC上手成本高、界面丑陋、跨平台无望纯Win32写界面代码量大得离谱一个简单的列表控件就要几百行。选Qt的理由很实在。第一Qt的跨平台特性意味着核心解析逻辑可以复用将来想跑在Linux上查看.so文件的版本信息虽然格式不同但架构可借鉴界面层几乎不用大改。第二Qt Widgets的成熟度足够高QTableWidget、QFileDialog、QDragDrop这些组件开箱即用省去大量界面代码。第三信号槽机制让文件解析这种可能耗时的操作可以很方便地放到独立线程避免界面卡死。还有一个很实际的原因Qt的QFileInfo和QDateTime类在处理文件元信息时非常顺手虽然版本信息需要自己解析PE结构但周边辅助功能Qt都帮你搞定了。2.2 核心解析方案自己读PE还是调系统API这是这个项目最关键的选型决策。Windows提供了现成的API来获取版本信息最常用的是GetFileVersionInfoSize、GetFileVersionInfo、VerQueryValue这一套。用系统API的好处是稳定、兼容性好微软帮你处理了各种边界情况。坏处是这些API是Windows独有的代码不具备可移植性而且某些特殊情况下比如文件被占用可能失败。自己解析PE文件的话需要理解DOS头、NT头、节表、资源节、版本资源的数据结构工作量不小但胜在完全可控而且能顺便学习PE格式。我的选择是两者结合优先使用系统API因为它是经过验证的可靠方案同时保留一个手动解析的备用路径用于处理API调用失败的边缘情况也方便将来移植。具体来说GetFileVersionInfoSize返回版本信息数据的大小GetFileVersionInfo填充数据缓冲区然后通过VerQueryValue查询具体的字段。查询时需要构造路径字符串比如\VarFileInfo\Translation获取语言和代码页然后拼接成\StringFileInfo\040904B0\FileVersion这样的路径来获取具体值。这个过程中语言代码页的获取是最容易出错的地方后面会详细讲。2.3 界面布局与交互设计界面设计遵循一眼看懂、三步操作的原则。主窗口分三个区域顶部是工具栏包含选择文件、选择目录、清空列表、导出CSV四个按钮中间是QTableWidget展示解析结果列包括文件名、文件版本、产品版本、公司名称、文件描述、版权、编译时间、文件大小、路径底部是状态栏显示当前处理进度和结果统计。拖拽支持是必须加的用户直接把文件或文件夹拖进窗口就能触发解析这个体验比点按钮选文件流畅得多。实现上重写dragEnterEvent和dropEvent判断mimeData()-hasUrls()然后遍历URL列表分别处理文件和目录。批量处理时用QThread或者QtConcurrent::run把解析逻辑放到后台线程通过信号槽把结果逐条发回主线程更新表格。这里有个细节如果一次性拖入几百个文件逐条更新表格会导致界面频繁重绘性能很差。我的做法是先在后台线程收集完所有结果再一次性批量插入表格或者用setUpdatesEnabled(false)暂停重绘插入完再恢复。3. 核心细节解析与实操要点3.1 PE文件版本资源的数据结构要理解版本信息从哪来得先知道PE文件的结构。一个Windows可执行文件或DLL遵循PEPortable Executable格式大致结构是DOS头MZ开头→ DOS存根 → NT头PE签名→ 文件头 → 可选头 → 节表 → 各个节的数据。版本信息存储在.rsrc资源节中具体来说是资源类型为RT_VERSION值为16的资源。版本资源的数据布局是一个VS_FIXEDFILEINFO结构体后面跟着若干个StringFileInfo和VarFileInfo块。VS_FIXEDFILEINFO包含固定的版本号字段比如dwFileVersionMS和dwFileVersionLS组合起来就是文件版本。而StringFileInfo里才是我们常看到的文件描述、公司名称这些字符串信息它们按语言和代码页分组。用系统API的话这些底层细节都被封装了你只需要调VerQueryValue传对应的路径就行。但理解这些结构对排查问题很有帮助比如当某个字段查不到时你就知道可能是语言代码页不匹配导致的。3.2 语言代码页的正确获取方式这是整个项目里最容易踩坑的地方。VerQueryValue查询字符串信息时路径格式是\StringFileInfo\语言代码页\字段名。这个语言代码页不是固定的不同文件可能不同。常见的有040904B0美国英语Unicode、080404B0简体中文Unicode等。正确的做法是先用VerQueryValue查询\VarFileInfo\Translation它会返回一个LANGANDCODEPAGE结构体数组每个元素包含wLanguage和wCodePage。取第一个元素的这两个值格式化成8位十六进制字符串各4位拼成%04x%04x的形式就是你要的语言代码页。我见过不少示例代码直接硬编码040904B0结果遇到中文软件就查不到信息。这个坑一定要避开。另外要注意有些文件的Translation块可能有多个条目稳妥的做法是遍历所有条目对每个都尝试查询取第一个成功的结果。3.3 编译时间的获取与格式化版本资源里其实没有直接的编译时间字段但PE头里有一个TimeDateStamp位于NT头的文件头部分表示文件链接的时间戳单位是Unix时间戳从1970年1月1日开始的秒数。这个值可以通过读取PE结构获取也可以用GetFileTime之类的API拿文件的创建/修改时间。两者有区别TimeDateStamp是编译器写入的链接时间理论上更接近编译时间但某些构建工具可能不更新它文件修改时间则是文件系统记录的更可靠但不一定等于编译时间。我的做法是两个都展示让用户自己判断。格式化时用QDateTime::fromSecsSinceEpoch转换注意时区问题一般用本地时间显示。读取TimeDateStamp需要手动解析PE头先读DOS头的e_lfanew字段偏移0x3C处4字节得到NT头偏移然后跳过4字节PE签名和20字节文件头在可选头之前的位置就是TimeDateStamp。实际上它就在文件头结构里偏移NT头8字节处。3.4 批量处理的性能优化批量解析几百个文件时性能瓶颈主要在磁盘IO和API调用。几个优化点第一用QDirIterator递归遍历目录时设置过滤器只处理.dll、.exe、.sys等PE文件避免解析无关文件浪费时间。第二解析结果先存到内存的QVector里全部完成后一次性更新UI。第三如果文件数量特别大上千个可以考虑用QtConcurrent::mapped做并行解析但要注意系统API的线程安全性实测下来GetFileVersionInfo系列API在多线程下是安全的。还有一个细节文件被其他进程占用时GetFileVersionInfoSize可能返回0。这时候不要直接报错可以尝试用FILE_SHARE_READ标志打开文件读取或者至少把文件名和无法读取的状态展示出来让用户知道哪些文件有问题。4. 完整实操过程与核心代码实现4.1 项目结构与构建配置项目用qmake构建结构很简单versionviewer.pro是工程文件main.cpp是入口mainwindow.h/cpp是主窗口versionparser.h/cpp封装解析逻辑。.pro文件里需要链接version.lib因为GetFileVersionInfo这些API在这个库里。QT core gui widgets TARGET VersionViewer TEMPLATE app SOURCES main.cpp mainwindow.cpp versionparser.cpp HEADERS mainwindow.h versionparser.h win32: LIBS -lversion如果是MSVC编译器-lversion要写成version.lib。MinGW下-lversion就行。这个细节不注意的话会报链接错误找不到GetFileVersionInfoSize的符号。4.2 版本解析核心类的实现VersionParser类提供一个静态方法parseFile(const QString path)返回一个结构体VersionInfo包含所有字段。核心逻辑分几步先调GetFileVersionInfoSize获取缓冲区大小失败则返回空结果然后分配缓冲区调GetFileVersionInfo接着查询Translation获取语言代码页最后逐个查询需要的字段。struct VersionInfo { QString fileName; QString fileVersion; QString productVersion; QString companyName; QString fileDescription; QString copyright; QString compileTime; qint64 fileSize; QString filePath; bool valid; };查询字段时封装一个辅助函数传入缓冲区、语言代码页和字段名返回QString。注意VerQueryValue返回的是void*字符串情况下是宽字符因为用了040904B0这种Unicode代码页需要转成QString。用QString::fromWCharArray或者直接强转wchar_t*再构造。编译时间的获取单独写一个函数手动读PE头。用QFile打开文件seek(0x3C)读4字节得到e_lfanew再seek(e_lfanew 8)读4字节得到TimeDateStamp。注意字节序是小端x86平台直接读就行跨平台的话要用qFromLittleEndian。4.3 主窗口的拖拽与批量处理主窗口构造函数里设置setAcceptDrops(true)然后重写两个事件函数。dragEnterEvent里判断是否有URL有就acceptProposedAction。dropEvent里遍历URL对每个路径判断是文件还是目录文件直接加入待处理列表目录用QDirIterator递归收集所有PE文件。处理时用一个QThread子类或者QThreadPool。我倾向于用QtConcurrent::run配合QFutureWatcher代码更简洁。把文件列表传给一个lambda在后台逐个解析每解析完一个通过信号发回结果。但前面说了逐条更新UI性能差所以改成后台全部解析完把QVectorVersionInfo通过信号一次性发回主线程收到后批量填充表格。表格填充时先setRowCount(0)清空然后setRowCount(n)用setItem逐格填充。为了性能可以关闭排序和更新tableWidget-setSortingEnabled(false); tableWidget-setUpdatesEnabled(false);填充完再恢复。4.4 导出CSV功能的实现导出功能用QFile写文本文件每行用逗号分隔字段。注意字段里可能包含逗号或引号需要做转义处理如果字段包含逗号、引号或换行就用双引号包裹内部的引号替换成两个引号。这是CSV的标准转义规则不处理的话Excel打开会错位。编码方面Windows下Excel默认用GBK打开CSV如果写UTF-8会乱码。解决办法是写UTF-8 BOM头\xEF\xBB\xBF这样Excel能正确识别。或者提供编码选项让用户选。我实测下来加BOM是最省事的方案。5. 常见问题与排查技巧实录5.1 版本信息查询失败的几种情况实际使用中查询失败的原因五花八门。最常见的是文件根本没有版本资源比如一些开源工具编译出来的exe就没写版本信息这时候所有字段都是空的属于正常情况界面上标注无版本信息即可。第二种是文件被占用或权限不足GetFileVersionInfoSize返回0。这种情况可以尝试用只读方式打开文件或者提示用户关闭占用程序。还有一种隐蔽的情况文件是32位但运行在64位系统上或者反过来某些API行为可能有差异但版本查询API本身是兼容的一般不受影响。第三种是语言代码页不匹配。前面强调过硬编码代码页会导致部分文件查不到字符串信息。排查方法是先查Translation块看实际有哪些语言代码页然后逐个尝试。如果Translation块本身查询失败可以回退到硬编码040904B0试试。5.2 编译时间显示异常的处理有时候TimeDateStamp显示的是1970年或者一个很离谱的未来时间这通常是因为文件不是通过标准链接器生成的或者时间戳被故意清零了。遇到这种情况可以回退到显示文件的修改时间并在界面上标注来源。用户一般更关心这个文件是什么时候的修改时间往往比链接时间更有参考价值。另外注意时区问题。TimeDateStamp是UTC时间QDateTime::fromSecsSinceEpoch默认返回本地时间显示时最好明确标注是本地时间还是UTC避免跨时区协作时产生误解。5.3 批量处理时的界面卡顿前面提到过用后台线程但即使这样如果一次性插入几千行表格控件本身也会卡。除了关闭重绘还可以考虑分页显示或者用QTableView配合自定义model后者性能更好但代码量大。对于这个工具几百个文件的场景足够用上千个的话建议加个进度条让用户知道程序没死。还有一个容易忽略的点拖拽大量文件时dropEvent里收集文件列表的过程本身可能耗时。如果拖入的是一个包含几万文件的目录递归遍历会卡住界面。解决办法是把收集文件列表也放到后台线程或者限制递归深度和文件数量。5.4 常见问题速查表问题现象可能原因解决思路所有字段为空文件无版本资源正常情况标注无版本信息部分字段查不到语言代码页不匹配遍历Translation块逐个尝试API返回0文件被占用或权限不足只读打开或提示关闭占用程序编译时间异常时间戳被清零或非标准链接回退显示文件修改时间界面卡死批量插入未优化后台解析批量更新关闭重绘CSV乱码编码不匹配写UTF-8 BOM头链接报错未链接version.lib.pro中添加LIBS -lversion5.5 几个实操中总结的小技巧第一个技巧解析结果缓存。如果用户反复拖入同一批文件可以按文件路径修改时间做缓存避免重复解析。用QHashQString, VersionInfo存key用路径value里带上修改时间下次先比对修改时间没变就直接用缓存。第二个技巧支持命令行参数。加个--cli模式传入文件路径直接输出结果到控制台方便集成到脚本里做自动化检查。这个功能实现起来很简单在main函数里判断argc和参数就行但实用性很高。第三个技巧高DPI适配。Qt5之后高DPI屏幕越来越普遍不处理的话界面会模糊。在main函数里设置QApplication::setAttribute(Qt::AA_EnableHighDpiScaling)Qt5.6以上还需要设置AA_UseHighDpiPixmaps。Qt6默认开启不用管。第四个技巧记住上次打开的目录。用QSettings存一下上次选择的路径下次打开文件对话框时定位到那里省得每次都要重新导航。这个体验优化很小但用户会觉得很贴心。6. 项目扩展方向与个人体会这个工具目前的功能已经能满足大部分日常需求但还有不少可以扩展的地方。比如支持查看依赖的DLL列表解析导入表这样能一眼看出某个exe依赖了哪些库再比如支持数字签名验证确认文件是否被篡改还可以加一个版本号比较功能批量检查一组文件的版本是否一致这对发版检查特别有用。从技术学习角度这个项目覆盖了Qt界面开发、多线程、文件IO、Windows PE格式解析、系统API调用等多个知识点麻雀虽小五脏俱全。我自己在写的过程中对PE结构的理解深了不少以前都是模糊知道个大概现在能说清楚版本资源具体存在哪、怎么组织。最后分享一个我在实际使用中的体会工具类软件的价值在于快和准。界面不需要花哨但响应要快、结果要准。我见过太多功能强大但操作繁琐的工具最后都被用户抛弃了。这个项目从设计之初就盯着三步内出结果的目标拖入文件、看表格、导出没有多余步骤。如果你也在做类似的小工具建议把核心流程打磨到极致比堆砌功能更有意义。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询