Windows 机密Windows 不是不能,而是不想

Raymond Chen

Windows 实际上可通过重命名原始文件,然后将新文件复制到位来替换正在使用的 DLL。但在 Windows 世界中最好不要这么做。为什么呢?

即使替换了正在使用的文件,系统中仍会存在想要使用旧版本的代码。例如,假定您有两个相互配合使用的文件 A.DLL 和 B.DLL,您发出了一个可以更新这两个文件的修补程序,但此时正在使用 A.DLL。没关系,只需同时替换这两个文件。结果是,使用 A.DLL 的程序仍使用旧版本,而新程序则使用新版本。但所有程序都使用新版本的 B.DLL。

正在使用旧版本 A.DLL 的程序现在决定调用函数。它通常期望调用旧版本的 B.DLL,但却获取了新版本。此调用的成功或失败取决于您对 B.DLL 所做的更改。这两个 DLL 都假定其合作伙伴来自同一个匹配集。

即使您现在只更新没有依赖关系的单个 DLL,潜在问题仍将存在,因为 DLL 必须与 以前的版本进行互操作。

假定您替换了正在使用的 ole32.dll。那么新启动的程序将调用新版本的 ole32.dll 并启动拖放操作。用户会将对象拖动到更新前启动的其中一个程序所创建的窗口中。您现在可以使用新版本的 ole32.dll 将消息发送给旧版本的 ole32.dll。

当您编写进程之间的通信代码时,您通常期望各个进程运行同一版本的代码,因为通信通道中的这两个端点必须针对其通信方式达成共识!“我要将消息 5(即要求文本)发送给您”并期望您将消息 12(即“这就是您要求的文本”)作为回复发送给我。如果这两端运行的代码不是同一版本,通信机制可能会不同,也许只存在细微的差别,但事实上仍会导致不兼容的情况。

假设您将一个字段添加到某个结构,而该结构正好用作消息 5 的一部分。很好。现在您有两个互不兼容的消息 5 版本 — 其中一个版本使用旧结构,另一个使用新结构。如果通信双方对于结构没有达成共识,消息 5 就无法正常工作。

如果您要并行运行旧版本和新版本,必须创建一个新消息(假定为消息 52),然后使新版本的 DLL 同时支持消息 5 和消息 52。但必须首先指出在另一端运行的 DLL 的版本并将相应的消息发送给它。

即使您尚未对结构本身进行更改,也可能您已更改了结构中某些字段的意义。如果该结构有一个枚举,且新版本将新值添加到该枚举,则新旧版本之间仍然不兼容。

当然,您可以尝试每发出一个修补程序就对新消息进行定义,然后编写代码以支持新旧版本在窗口中的两个相互冲突的版本 DLL 同时运行期间的互操作性。但是这样一来,不实用的消息集就会越来越多,当您发到第三个修补程序时,就已经拥有四个不同的消息集了(其中一个针对原始版,另一个则是针对每个修补程序)。

即使您要求这些修补程序必须按顺序进行安装,也不会为您节省任何精力;因为只要您允许运行原始程序,就必须继续与它维持互操作性。

然后就有人开始写文章抱怨您是个白痴,因为您发出修补程序的速度实在太慢了!

所以,Windows 并不是不想面对替换正在使用的文件后必须重新启动的麻烦,而是不想面对因为不执行重新启动所带来的麻烦。工程就是一组权衡。您是否愿意花费精力支持旧版本,只为获得一种连推荐的稳定状态配置都算不上的环境?

Raymond Chen 的网站“The Old New Thing”及同名著作讲述了 Windows 的发展历程和 Win32 编程。他祝您一切顺利。