
同一个组件,A 程序要用 1.0 版,B 程序要用 2.0 版——而它们找文件的地方往往是同一个系统目录。
这就是当年著名的"DLL 地狱":谁最后装,谁说了算;先装的那个程序就坏了。
系统给出过一个正式的解决方案,叫并行程序集。它带来的机制到今天还在影响程序的加载行为——也解释了一批用 DLL修复工具 解不开的困惑。
问题的根源是按名字找文件:
结果就是:装新版,老程序坏;装回旧版,新程序坏。而这个链条经常是安装另一个不相干的软件导致的。
思路一:让每个程序带自己的私有副本。
把需要的组件放在程序自己的目录里。因为默认的查找顺序里程序目录优先,所以它会先找到自己那份。
优点:彻底隔离,改一个不影响别的。
缺点:每个程序各带一份,磁盘占用翻倍;更麻烦的是——安全更新覆盖不到它们。系统组件被修补了,但你程序里那份私有副本还是旧的。
思路二:让系统能同时保留多个版本。
不把文件放在"一个系统目录、一个命名空间"里,而是按若干要素区分并存。
系统收录这类组件时,会按名称 %2B 版本 %2B 架构 %2B 语言等要素分别登记、分开存放。于是同名不同版本可以同时存在,各自被需要的程序找到。
这就是并行程序集(Side-by-Side)的核心思路。
这里是关键的一步。
程序(或它依赖的组件)可以附带一份声明——通常叫清单。它写明了:我依赖哪些组件、要哪个版本、什么架构。
加载器在解析依赖时,会先读这份声明,然后按声明里的要求,去并行存储里精确取对应那一份,而不是去系统目录里抓一个同名文件。
这一条推翻了很多人对加载的默认理解:
不是"按名字从系统目录拿一份",而是"按声明要求,取指定那一份"。
两种常见形式:
作为资源嵌在可执行文件或模块内部。 这是最常见的形式,文件本身带着它。
作为外部文件放在程序目录里。 名字通常与程序或模块同名。
外部形式的清单有个很容易踩的坑:它依赖路径。 把程序移动或改名之后,外部清单可能就配不上了——表现是行为突然变化(换了另一个版本的组件),而程序本身完好无损。
现象一:替换了系统目录里的文件,程序行为没变。
因为它按声明加载的是并行存储里那份,你替换的不是它要的那一份。
现象二:系统更新了组件,但程序表现还是老样子。
如果程序带私有副本,或者声明指向了特定版本,那么系统里那份更新与它无关。这既是隔离的好处,也是"补不上安全修补"的代价。
现象三:程序移动位置后行为变了。
外部清单没有跟着走,或者路径变了。
现象四:同一个组件的两个版本,能同时被不同程序使用。
这正是这套机制要达到的效果,属于预期。
关系在于:加载失败的报错可能指向"清单层面",而不是"文件层面"。
这时会出现一种很别扭的情况:
这类报错用"补齐文件"的思路是解不开的——你把系统目录那份补上、修好,它要的还是另一份。所以 DLL修复工具 这类按名称查找并补齐的工具,在这里往往扫描不出问题,或者修完仍然照旧。
判断的线索是:报错点名某个组件,但那个组件明明在系统里存在。 出现这种情况,DLL修复工具 补不上、也扫描不出异常,方向就该转到“它在按什么要求找”上。
第一,新项目优先自带依赖或使用私有副本。
隔离带来的确定性,远大于省下的那点磁盘空间。尤其是依赖行为可能随版本变化的组件。
第二,不要假设系统目录里存在某个特定版本。
那是在赌别人机器上的安装历史。要某个版本,就明确声明,或者自己带着。
第三,清单要与程序一起部署,并保持相对位置稳定。
移动程序、改名、只复制主程序而漏掉清单——这些都会改变加载结果,而现象看起来像"程序换了个脾气"。
关于"同一个组件的多个版本为什么能共存",记住四条:
这套机制留下的一个通用经验是:当多个使用者需要同一个东西的不同版本时,"让所有人共用一个"是最省事也最脆的方案。隔离与显式声明看起来麻烦,但它把"碰运气"换成了"可预期"——这一点在依赖管理上,几乎所有场景都成立。
https://www.ijinshan.com/functions/repairdll.html?channel=4083
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。