Python 3.8 上 ctypes 加载 DLL 库抛出异常 FileNotFoundError: Could not find module 'node.dll'

作者:一年又一年 分类: 📐 技术 发布时间:2020-1-1 14:52 30298 次浏览 0 条评论

本文原发于笔者在 Github 上的项目 Issue 讨论中 (https://github.com/ynyyn/Miniblink-Python-SimpleDemo/issues/4),系笔者在 Python 3.8 上测试项目兼容性时觉察到的 Python 行为(与兼容性)变更。

现象

使用 Python 3.8 ctypes 加载 DLL 库,采用相对路径加载,如下写法所示:

mb = ctypes.cdll.LoadLibrary("node.dll") 

启动时抛出 FileNotFoundError 异常,提示 Could not find module 'node.dll'. Try using the full path with constructor syntax.

Exception in thread Thread-1:
Traceback (most recent call last):
  File "...\Python38\lib\threading.py", line 932, in _bootstrap_inner
    self.run()
  File ".\main.py", line 16, in run
    mb = ctypes.cdll.LoadLibrary("node.dll")
  File "...\Python38\lib\ctypes\__init__.py", line 451, in LoadLibrary
    return self._dlltype(name)
  File "...\Python38\lib\ctypes\__init__.py", line 373, in __init__
    self._handle = _dlopen(self._name, mode)
FileNotFoundError: Could not find module 'node.dll'. Try using the full path with constructor syntax. 

原因

导致该问题的原因是:Python 3.8 变更了 Windows 下动态链接库 (DLL) 的加载规则。

新的规则提高了安全性,默认情况下仅能从可信的位置(Trusted Locations)加载 DLL 依赖,一定程度上避免诸如 DLL 劫持之类的安全风险。

ctypes

On Windows, CDLL and subclasses now accept a winmode parameter to specify flags for the underlying LoadLibraryEx call. The default flags are set to only load DLL dependencies from trusted locations, including the path where the DLL is stored (if a full or partial path is used to load the initial DLL) and paths added by add_dll_directory(). (Contributed by Steve Dower in bpo-36085.)

参阅: https://docs.python.org/3/whatsnew/3.8.html#ctypes

The winmode parameter is used on Windows to specify how the library is loaded (since mode is ignored). It takes any value that is valid for the Win32 API LoadLibraryEx flags parameter. When omitted, the default is to use the flags that result in the most secure DLL load to avoiding issues such as DLL hijacking. Passing the full path to the DLL is the safest way to ensure the correct library and dependencies are loaded.

Changed in version 3.8: Added winmode parameter.

参阅: https://docs.python.org/3.8/library/ctypes.html#ctypes.CDLL

由以上资料可知,可信的位置包括:

  • DLL 所在的路径(加载 DLL 时提供 DLL 的完整路径或部分路径,文件存在则该路径即可信)。
  • 使用 add_dll_directory() 添加的路径。
  • (系统可信位置)

因此,下面这种之前可行的写法在 Windows Python 3.8 下会导致异常:

mb = ctypes.cdll.LoadLibrary("node.dll")   # 指定了 DLL 名,Python 3.8 下抛出异常 
                                           # `FileNotFoundError: Could not find module 'node.dll'` 

该语句指定待加载的模块名node.dll,但由于当前工作目录不在可信位置里,故不会搜寻当前目录下的 DLL,最终导致无法在可信位置里找到名字匹配的模块,遂异常。

而下面的写法则不会异常:

mb = ctypes.cdll.LoadLibrary("./node.dll")   # 指定了 DLL 的路径 

因为 ./node.dll 实际上是给出了模块路径而不是模块名,当显式地给出模块路径时,模块存在则该路径属于可信位置,可以加载。

可行的解决方案

由以上资料,至少可得到三种解决方案:

  1. 通过 add_dll_directory() 把 DLL 目录加入可信列表。
  2. 使用 ctypes.XXXDLL() 加载 DLL 时指定 winmode 参数(该参数将指定底层调用 Win32 API LoadLibraryEx 时所使用的 flags),将值指定为可以从本地路径加载,替换掉默认行为。
  3. 使用 ctypes.XXXDLL() 加载 DLL 时为 DLL 指定路径,而不仅是指定 DLL 名。

注:ctypes.XXXDLL() 中的 XXXDLL 泛指 CDLL, PyDLL, WinDLL 或 OleDLL

Windows Python ctypes dll

♥ 若您欲转载敝站的原创内容,还请您附注出处及相应链接

发表评论

* 标注的项目为必填项。

您的邮箱地址将不会在页面中公开
您的站点地址将会被检查,如被认为不适则可能被移除
Ɣ回顶部