简介:DependenciesGui-windows10-depends 是一款面向 Windows 10 环境的动态链接库依赖分析工具,由 Visual Studio 2019 编译生成,采用 64 位架构,主要用来帮助用户快速定位程序运行时的 DLL 缺失、组件不匹配等问题,也可供开发者了解自己软件的依赖构成,从而更合理地规划发布包内容。压缩包采用 7z 格式,整体仅 1.78MB,共包含 35 个文件,其中既有多数的 DLL 动态库和 EXE 可执行文件,也配有 PDB 调试符号、CONFIG 配置文件以及 XML 文档,便于在分析依赖时同步查看符号信息和运行参数。当前已有 673 人学习下载。该工具能够加载任意可执行文件并快速列出完整的依赖清单,包括组件版本、路径等细节;部分版本还支持递归展开次级依赖,方便梳理多层引用关系。对于普通用户而言,它是排查程序无法启动的实用助手;对开发者而言,它也能作为理解依赖链、优化部署方案的辅助工具,在复杂场景下可与其他专业工具配合使用。
1. DependenciesGui 不是 depends.exe 的简单复刻:Windows 10 排查 DLL 缺失的新选择
在 Windows 10 上装了个新程序,双击没反应,或者弹窗告诉你“找不到 VCRUNTIME140.dll”,这种崩溃现场每个月都要碰几回。老手的第一反应是掏出 depends.exe,但 Dependency Walker 停更多年,在 Win10 上越来越像“色盲”,很多系统 DLL 认不出来,满屏红字分不清真假。DependenciesGui 就是接替它的开源工具:把 exe 或 dll 拖进去,递归展开整个依赖树,标注缺失模块,还能导出 JSON 和 CSV。这文写给做软件分发、做安装包、以及常被绿色软件坑的运维和开发,目标是让你把“依赖玄学”变成“可查清单”。
2. 依赖分析在查什么:导入表、延迟加载与 ApiSet 虚拟 DLL 的展开逻辑
2.1 为什么 depends.exe 在 Windows 10 上会“色盲”:旧工具读不懂 API Set
先明确一个事实:程序运行时缺 DLL 的“缺”,大部分不是文件不在,而是导入表的某个符号在系统里找不到承载它的人。Windows 10 从 1809 开始大量使用 API Set Schema,系统把 kernel32.dll、kernelbase.dll 的很多函数重新组织成 api-ms-win-core-xxx.dll 这样的虚拟 DLL。实际调用时,系统通过 ApiSetMap 把虚拟名映射到真实文件。旧版 depends.exe 是在 XP 时代设计的,它不知道这套映射,会把 api-ms-win-core-xxx.dll 当成真实文件去文件系统里找,找不到就标红。于是 Windows 10 上跑 depends 经常满屏红,最后发现程序其实跑得好好的,这就是“假红”。
DependenciesGui 的思路是解析 PE 导入表之后,先过一遍当前系统的 API Set 映射表,把虚拟 DLL 翻译成真实模块;翻译不了的,再回到文件系统里找。所以同样一个程序,旧工具报缺 30 个 DLL,它可能只报缺 1 个。这一点在实际排查里价值很大,尤其是从 Win7 迁到 Win10 的老程序,一打开满屏 api-ms-win-* 的红字,新手很容易被带偏去网上找一堆根本不存在的补丁。先搞懂“哪些红是假的”,后面才谈得上定位真问题。
顺便说一下架构匹配:如果目标 exe 是 32 位的,系统里存在两套 DLL(syswow64 和 system32),搜索路径不对就会拉到另一套。这个工具的分析引擎会优先站在目标架构上做解析,但前提是你给它指定的搜索路径里不混入错误架构的副本。这一点到第 5 章避坑部分再展开。
2.2 解析顺序:从 IAT 到延迟加载,再到递归展开的边界
一个 PE 文件到底依赖谁,信息其实都在文件头里。打开 exe 后,解析引擎先读导入表(IAT),拿到“要加载哪些 DLL、从每个 DLL 导入哪些函数”的清单;然后把清单里的模块逐个在搜索路径里定位,找到后再递归打开这些 DLL,继续读它们的导入表,直到没有新模块为止。这个递归是工具的核心,也是它比“用 Process Explorer 看已加载模块”强的地方——后者只能看到运行时快照,不能告诉你“为什么这个 DLL 没被加载”。
另一个容易忽略的点是延迟加载(Delay Load)。很多程序把不常用的功能放在延迟加载的 DLL 里,启动时不加载,用到某个功能时才 LoadLibrary。DependenciesGui 会把延迟加载的依赖单独列出来。排查时要区分:普通导入的 DLL 缺失会导致程序无法启动,延迟加载的 DLL 缺失只在触发对应功能时才崩。我在第 4 章给的实际场景会专门讲这个差别。
递归展开的边界也需要说清楚。工具不会无限展开下去,它有自己的停止条件:到达系统目录下的已知模块、遇到循环依赖、或者某个模块在搜索路径里彻底找不到就停。停在“找不到”时,这个节点就是你要追的嫌疑人。所以看待依赖树不要只看它展开了多少层,要看它停在哪一层、为什么停。展开深度如果设得太浅,比如只展开 2 层,有些真实的缺失会被隐藏在更深层,后面就会在运行时报错,这就是为什么默认情况下不要轻易把深度调小。
提示:分析一个目录里的整套程序时,最好把整个软件包目录加进搜索路径,否则工具只看得到系统目录和程序目录,第三方库放在相对路径下的就找不到了。
3. 在 Windows 10 上跑通最小验证:命令行导出依赖树 JSON 与 CSV
3.1 第一步:先跑 -help,把工具的参数表拉出来
DependenciesGui 在不同时期版本里的命令行参数有过调整,没有哪份网上的命令能保证直接抄了就能跑。所以我拿到一个新版本,做的第一件事永远是切到工具目录跑一次 -help,把参数表拉出来对一遍,再决定怎么拼命令。这一步花三十秒,能省掉后面半小时瞎试。
# 切到工具解压目录,我的习惯是放 C:\Tools\DependenciesGui cd C:\Tools\DependenciesGui # 查看当前版本支持的命令行参数 .\Dependencies.exe -help跑完你会看到一串参数列表,不同版本名字可能不太一样。常见的有:指定输入文件的、指定输出目录的、选择 JSON/CSV 输出格式的、限制展开深度的、控制是否跳过已知系统 DLL 的。我的做法是把这份 help 输出存成一个 txt 放到工具目录里,下次换版本先 diff 一下,看哪些参数改名了,比每次重新啃帮助快得多。命令行工具最怕的就是“照着旧教程敲,然后参数被悄悄改了”,提前对一遍参数表是唯一的后悔药。
3.2 最小命令:导出 JSON 依赖树并核对缺失节点
帮助确认过之后,找一个你知道肯定会缺 DLL 的小 exe 来试,比直接上生产程序稳。下面这段是我常用的最小流程。命令里的具体参数名用你刚刚 -help 里看到的为准,这里给出的是通用逻辑:
# 定义工具路径和目标程序路径,后面改成你自己的 $dep = "C:\Tools\DependenciesGui\Dependencies.exe" $target = "D:\test\broken-app.exe" # 把依赖树导出成 JSON,输出到当前目录的 deps 文件夹 & $dep -chain -json $target -out "D:\test\deps" # 导出完之后,列出输出目录里的文件 Get-ChildItem "D:\test\deps"这段命令里-chain表示要生成完整的依赖链,-json指定 JSON 格式,-out指定输出目录。输出的是当前工具的 JSON 结构,一般每个节点会带上模块路径、是否缺失、是否系统 DLL、导入导出函数列表这些字段。拿到 JSON 之后,我一般用 PowerShell 再筛一遍“标记为缺失的节点”,比在 GUI 里肉眼找红字要可靠得多:
# 把 JSON 读进来,筛选 marked 字段为 missing 的节点 $json = Get-Content "D:\test\deps\broken-app.exe.json" -Raw | ConvertFrom-Json $missing = $json.modules | Where-Object { $_.missing -eq $true } $missing | Select-Object name, dllpath | Format-Table -AutoSize这段筛选把“缺失模块”单独拉出来,数量和名字一目了然。要留意的坑是:不同版本导出的 JSON 字段命名可能不同,可能是missing、可能是status、也可能是isMissing。所以筛之前先$json.modules[0].PSObject.Properties.Name看一下字段清单,别按我的字段名硬套。这一步能过滤掉 80% 的字段坑。
3.3 GUI 兜底:窗口里直接看红色节点与可定位的模块路径
命令行适合批量跑和数据集比较,但人肉排查时我还是会开 GUI 界面。DependenciesGui 的界面左边显示依赖树,右边是选中模块的属性。双击一个红色节点,可以直接看到它在系统里的搜索路径和匹配结果。比对着 JSON 猜路径直观得多。我的日常习惯是:命令行导出 JSON 做全量记录,GUI 用来辅助定位具体某一个 DLL 为什么找不到,两者配合而不是二选一。
GUI 下还有一个命令行不方便的优势:能实时看导入函数的解析状态。有时候模块文件在,但某个导出函数在文件里不存在,GUI 会把那一条标出来;命令行输出的 JSON 里这类细节也存在,但需要你展开很多层才能发现。现场排查时,节奏很重要——先看大块的红色区域,再钻进去看某一条函数级别的失败,GUI 走得更快。若目标程序是打包进安装包的,拖拽进界面之前先把安装包解压到一个干净目录,别直接在压缩包内双击,否则工具读的是临时解压目录,路径信息全乱。
4. 三张现场排查清单:运行时补 DLL、VC++ 运行库缺失与版本回归对比
4.1 场景一:启动报“找不到 VCRUNTIME140.dll”,缺的是 VC++ 运行库
这个报错被问的次数最多,现象是程序启动弹窗:“找不到 VCRUNTIME140.dll,无法继续执行代码”。很多人第一反应是去系统目录里翻这个文件,翻到了就复制到程序目录,然后程序还是起不来,或者换一台机器又挂。这个坑我建议直接用 DependenciesGui 看清全貌再动手。
VCRUNTIME140.dll 属于 VC++ 2015-2022 Redistributable 运行库,它不是单独一个文件在工作,而是 msvcp140.dll、vcruntime140.dll、vcruntime140_1.dll、concrt140.dll 这一组协同。只补一个文件,等于让一个人干四个人的活,不崩才怪。正确做法是先用工具分析目标 exe 的依赖树,看这组 DLL 里哪些标了缺失,再决定是装完整运行库,还是只复制缺少的那几个。判断标准很简单:如果依赖树里这组缺失超过 3 个,直接装 Redistributable;如果只缺 1 个且手头有同架构的正版副本,才考虑单文件复制。这个取舍决定了你是在“修问题”还是在“踩新坑”。
4.2 场景二:绿色软件带了一堆 DLL,哪个是真正被加载的
绿色软件的解压包里经常躺着几十个 DLL,有的在根目录、有的在子目录。程序启动正常,但功能触发时崩,这种间歇性故障最像玄学。实际原因经常是:包里的某个功能模块延迟加载了一个根目录外的 DLL,而那个 DLL 不在搜索路径里。DependenciesGui 会把延迟加载项和普通导入项分开列,我排查时只看两个地方:一是标红的普通导入项,二是标红的延迟加载项。
如果是延迟加载项缺,启动不会报错,所以必须主动去“触发那个功能”才能复现。做法是:先导出一份完整 JSON,记录所有延迟加载的 DLL;然后在程序里逐个触发你认为可能出问题的功能;每次崩了之后回来对比,看是不是崩在之前标红的那一项上。这种排查方式依赖的是“依赖树先行”的思路,比崩溃后用调试器看调用栈要快得多。因为崩溃栈告诉你崩在哪,不告诉你为什么那个 DLL 没被正确加载,而依赖树直接给出了缺失清单。
4.3 场景三:版本升级后依赖变化,用 CSV diff 做回归
软件版本升级后出问题的概率,永远比全新部署高,因为用户环境里残留着旧版本的 DLL。这类问题最适合交给 CSV 输出加 diff 来做:发版前把当前版本的依赖树导成 CSV 存档,升级后导一份新的,然后比对差集。新增的 DLL 列表、减少的 DLL 列表、架构变化的 DLL 列表,三张表一出来,问题基本就能定位到具体模块。
# 假设旧版本依赖已经导出为 old.csv,新版本刚导出为 new.csv $old = Import-Csv "D:\rel\old.csv" $new = Import-Csv "D:\rel\new.csv" # 找出新增的 DLL 和消失的 DLL $newAdded = $new | Where-Object { $_.name -notin $old.name } $oldGone = $old | Where-Object { $_.name -notin $new.name } $newAdded | Format-Table name, path $oldGone | Format-Table name, pathCSV 的好处是每一行就是一个模块,列里有名称、路径、架构、缺失状态。diff 出来的新增项要认真看:如果是带上版本号后缀的 DLL 更新,属于正常;如果新版本改依赖了一个完全陌生的模块,就要确认这个模块是安装包带进来的,还是指望系统里有。这里也是“测试环境一切正常、客户机器一跑就挂”最常见的翻车点——客户机器上恰好没有你测试机里残留的那个旧 DLL。把这份 diff 的结论写进发布说明,能让售后少接一半报障。
5. 避坑记录:DependenciesGui 在 Win10 上的 5 个误判现场
5.1 大面积红色并不是真的缺失:搜索路径没配对
现象:分析一个程序,依赖树里大半边标红,看起来这场故障很大。原因:工具没加搜索路径,程序目录下的第三方 DLL 全部“找不到”,红字全是假警报。解决:分析前把整个软件包根目录加进搜索路径,而不是只加 exe 所在目录;加了之后再重新分析,红字数量通常能减少九成。这点在 2.2 末尾提过,实操里最容易被忽略,因为 GUI 打开后默认只分析,很多人根本不知道搜索路径在哪设置。
5.2 32 位程序拖进 64 位工具实例,依赖树直接“缩水”
现象:分析一个 32 位 exe,树看起来比预想短,而且好多系统 DLL 显示不出来。原因:工具实例的位数和目标程序不一致。Windows 10 上 64 位工具默认搜 system32 里的 64 位 DLL,32 位程序的实际系统目录是 SysWOW64,搜错区域自然匹配不上。解决:DependenciesGui 如果提供 32/64 两个版本,就用与目标 exe 相同位数的那一个来分析;没有多版本时,手动把 SysWOW64 加进搜索路径。这个问题在“用 64 位工具看 32 位程序”的场景里是必踩项,不配对的话,你分析出来的树就是黑匣子里的残次品。
5.3 同路径 DLL 被重复识别,循环依赖刷屏
现象:依赖树里同一个 DLL 出现好多次,或者在节点之间来回跳,看起来像死循环。原因:模块 A 依赖模块 B,模块 B 又反向依赖模块 A,这是真实存在的循环依赖;工具为了展示完整性把同一文件的多层依赖都画出来了,视觉上像重复。解决:先检查是不是同一路径的同一文件;如果是,直接在展开选项里关闭循环递归,或者把分析深度调到能覆盖实际调用链的层数。循环依赖本身不代表错误,关键是分清“重复出现”和“真正多版本共存”——同一 DLL 有多个不同路径,才是需要警惕的多版本问题。
5.4 命令行导出和 GUI 看到的结果对不上
现象:同一份 exe,GUI 里看着能解析某个 DLL 的导出函数,命令行导出的 JSON 里却标记缺失。原因:GUI 和命令行默认使用的配置不同,最常见的变量是“是否包含已知系统 DLL”的开关,以及搜索路径列表。解决:命令行跑之前,把配置文件里跟 GUI 一致的那份参数带上;不要用默认配置去跟 GUI 结果对照。这类不一致最容易在写自动化脚本时踩到——脚本里标红的一堆,人工看却没事,最后发现是两个入口用的参数不是一套。
5.5 Windows 10 实时保护拦截,工具静默失败
现象:工具双击没反应,或者分析到一半退出,命令行报错也看不出来。原因:分析 PE 文件的行为容易被 Windows Defender 实时保护盯上,个别情况还会隔离工具本体。解决:把工具目录加进 Defender 排除项再试;如果公司机器有安全策略不允许改,就用命令行模式在受控目录跑,导出结果后马上把工具移走。另外“以管理员身份运行”也能减少一部分被拦截的概率,但根上还是排除项配置的事。
6. 把依赖检查写进发布流程:快照对比脚本与干净的虚拟机验证
6.1 快照 diff:把上一次发布目录留着做基线
我现在的习惯是每次发版前都跑一次依赖导出,文件名带上版本号和日期,比如myapp_v2.3.0_20251001.json,存到一个只追加不删除的目录里。这个目录就是基线库。下次任何人报“升级后缺东西”,我直接把两个版本 JSON 做一次 diff,十分钟内能答复“旧版依赖了什么、新版新增依赖了什么、缺的是哪一个”。这个动作成本极低,但能救很多次现场。
6.2 干净虚拟机:22H2 最小环境里跑一遍启动自检
再快的 CLI 分析也比不上一台干净的 Windows 10 虚拟机来得实在。我通常在虚拟机里装一个 22H2 官方镜像,只装系统不装开发环境,然后把软件包复制进去,在最小环境里跑启动自检。如果依赖树分析显示某个 DLL 缺失,而干净环境里确实没有,那就需要在安装包里内置它。这一步能提前暴露“依赖了用户机器上的环境残留”这类问题。日常顺序是:先 DependenciesGui 分析,再虚拟机跑一遍,最后才走发布。
我自己的习惯是这个虚拟机永远不装 Visual Studio、不装运行库全家桶,保持最瘦状态。因为一旦装全了,它就不再“干净”了,测试结果就没有说服力。作为常年跟 DLL 缺失打交道的人,我吃过太多次“测试环境跑得好好的、用户机器一跑就挂”的亏,后来的补救就是把上面这套流程变成发布前的固定动作。希望帮到你。
本文还有配套的精品资源,点击获取