Windows下Python中常见的几种DLL load failed问题的原因以及解决方案

Windows下Python中常见的几种DLL load failed问题的原因以及解决方案

原博客文章地址: blog.peterjc.stream/202

问题背景与相关知识

在深度学习流行的当下,深度学习的框架大多是基于Python的实现,抑或是提供了Python的接口。而为了保证性能,底层计算通常是使用C/C++实现的。对于C/C++项目,其链接方式主要可以分为以下两种:

  • 静态链接方式 (Static linking)
    静态链接即在链接时即确定程序会包含哪些模块。由于在链接时已经确定了所有包含的模块,那么会直接将这些模块打包,生成一个新的可执行文件或者静态链接库文件。
    因而这种方法具有如下特点:
    • 分发简单。因为其不存在运行时才能解决的依赖。
    • 由于无法事先知道用户会使用其中的哪些模块以及哪些函数,因而产生的静态链接库会比较大。


  • 动态链接方式 (Dynamic linking)
    动态链接方式即在链接时仅确定包含模块的名称、以及其中所用到的函数,生成一个可执行程序或是动态链接库(DLL)。而在运行时,根据这些信息来寻找相应的模块进行加载。
    这种方法的特点有:
    • 由于在运行时才加载对应的模块,因而可以将模块进行替换。
    • 由于所依赖的模块没有被包含进来,因此生成的动态链接库不会太大。
    • 由于动态的加载,因而会受用户环境的影响,因而在分发时有可能产生问题。


在目前 Windows 的大多数深度框架下,大多采用动态链接方式。其原因在于

  1. 深度学习框架中需要包含许多 OP 的实现,这些实现需要覆盖多种指令集、多个 CUDA 架构,因而生成的目标文件会比较大。而64位 Windows 下对于单个模块要求大小不能超过4GB。
  2. 某些模块不提供静态链接库,比如 cuDNN 等。

在动态链接方式的第三个特点中,我们说到了分发时可能会产生问题。这类问题通常被称为"DLL Hell"。可以考虑下面一个场景:

程序 P1 依赖于 库 L 的一个版本 L1。用户为了能使用 P1,将 L1 放在系统目录中。而某一天,用户又需要使用程序 P2,而 P2 则依赖于 库 L 的另一个版本 L2。假设 L1 和 L2 是无法互相兼容的,那么用户就无法同时使用 P1 和 P2。

为了解决这样一种问题,有如下的一些解决方法

  1. 每个程序自带一份动态链接库的局部拷贝。即 P1 分发时带上 L1,而 P2 分发时带上 L2,这样两者就不会互相干扰。但是很明显,这种方式会导致一个动态链接库的多次拷贝,造成空间的浪费。
  2. 很明显 L1 和 L2 会互相冲突,是因为两者的名称一样,那么对于无法互相兼容的包,直接取不同的文件名就完了。这正是 Unix 下的解决方法,库文件的取名为 libxxx.so.主要版本.修订版本号 。如果一个库无法与之前的版本互相兼容,那么需要增加主要版本号,否则可以仅增加修订版本号。此外,为了使用方便,一般还会生成一个不带修订版本号的软连接 libxxx.so.主要版本 指向当前主要版本的最新库文件。

虽然方法2看上去是一个不错的方法,但是它依赖于一个命名规则。这一方面不是强制要求,另一方面若仅是开发者用于调试,则非常的不方便。而对于方法1,如果某个程序还是将库安装到了系统目录,那还是会造成问题。如果我们可以额外设定一些信息来辅助动态链接库的查找,那么问题就迎刃而解了。这个信息在 Unix 中被称为 RPATH ,在编译链接时指定,而对于 .NET 平台,则可以新建一个 .config 文件来指定动态链接库的目录。

很可惜,由于Windows平台上所背负的沉重包袱,没有类似引入这样的解决方法。此外,由于没有统一的包管理器,因此方法2也是无法使用的。然而,即使我们采用了方法1,也无法保证程序可以正确的载入所有依赖库。在一般情况下,在DLL加载失败时,Windows会跳出一个消息框来提醒用户哪个DLL加载失败了。但是,Python 为了保证代码不会被UI阻塞,因此将这个消息框弹出的消息屏蔽了,这使得调试这个问题变得更为复杂。

下面我们将具体的介绍这个问题的产生原因以及解决方法。在介绍Python下常见的几种DLL load failed错误之前,我们可以将其分为两种类型。

  1. 静态 DLL 加载错误。即 DLL 库自身没有逻辑错误,但是由于缺少依赖项、加载了错误版本的 DLL 导致加载失败。这种错误使用静态调试工具即可解决。
  2. 动态 DLL 加载错误。即 DLL 库的自身逻辑可能存在问题,在初始化时便遇到未处理的异常,导致 DLL 加载失败。这种错误仅能使用动态调试工具解决。

常见的Python下的几种DLL load failed错误

  1. DLL 缺失
    常见的错误消息:
ImportError: DLL load failed: The specified procedure could not be found.
ImportError: DLL load failed: The specified module could not be found.
ImportError: DLL load failed: 找不到指定的程序
ImportError: DLL load failed: 找不到指定的模块
  1. 问题背景与相关知识
    很明显,这种错误就是缺少所依赖的 DLL 导致的。由于 DLL 中包含了所依赖的 DLL 的相关信息,因此可以通过循环的枚举查找对应的 DLL 来模拟 DLL 的加载过程来调试这个问题。当然,我们也可以动态的追踪系统如何解析、查找并加载 DLL 来找出具体缺少的 DLL。
DLL 不兼容
常见的错误消息:
ImportError: DLL load failed: The operating system cannot run %1.
ImportError: DLL load failed: 操作系统无法运行%1