关于Python38中调试PyQt5时import PyQt5.QtCore报错(ImportError: DLL load failed)的问题分析

Python更新到了Python38,有一个非常好的更新,统一了Debug与Release模式下的ABI,也就是说在C/C++中嵌入Python时,可以直接在Debug模式下用Release的Python了,果断考虑从Python37更新到Python38。想当初为了调试在Debug模式下的C/C++&Python混合代码,把Python的模块都编译了个遍~~~~。

然而事情往往总是不会一帆风顺,在把代码移植后,本以为会执行得很好的代码报错了:

ImportError: DLL load failed while importing QtCore: 找不到指定的模块。

思考:这份代码在Python37下运行正常,会是什么原因呢?

1、dll系统搜索路径不对

加path解决,无效,看来不是这个原因导致的。

2、试试执行python3.exe 再执行import PyQt5.QtCore会不会报错

没有报错,但是仍然不知道是什么原因导致的。

3、将程序拷贝到Python目录,直接运行

没有报错,看到了成功的希望,但是这个不是解决问题的办法,还得继续找原因。

4、vs以调试模式运行拷贝到python目录的程序

仍然运行正常,还是对问题未知。(此时已一头雾水)

5、用DEPENDS.EXE分析QtCore.pyd对库的依赖,在执行import PyQt5.QtCore之前将这些依赖调用LoadLibraryA加载

过程中加载了Qt5Core.dll、Python3.dll、sip.cp38-win32.pyd,能正常运行

尝试只加载Python3.dll,依然能正常运行,此时貌似在程序运行前执行下LoadLibraryA("Python3.dll")能解决,但是没有找到导致这个问题的根本原因

6、分析Python代码,搜索LoadLibrary字眼

定位到dynload_win.c _PyImport_FindSharedFuncptrWindows函数,在该函数添加条件断点,当加载QtCore.pyd时断点

_PyImport_FindSharedFuncptrWindows调用了LoadLibraryExW,

这里有代码修改的注释:

/* bpo-36085: We use LoadLibraryEx with restricted search paths
           to avoid DLL preloading attacks and enable use of the
           AddDllDirectory function. We add SEARCH_DLL_LOAD_DIR to
           ensure DLLs adjacent to the PYD are preferred. */
Py_BEGIN_ALLOW_THREADS
hDLL = LoadLibraryExW(wpathname, NULL,
                              LOAD_LIBRARY_SEARCH_DEFAULT_DIRS |
                              LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR);
Py_END_ALLOW_THREADS

从注释来看,这里比较python37进行了更改,通过查找windows api手册,发现LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR将不会在PATH中配置的路径中去寻找库,并且不会加载非程序目录之外的dll。通过分析可知,QtCore.pyd依赖Python3.dll,而Python3.dll位于PATH环境而非应用程序目录,在用加载LoadLibraryExW LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR加载QtCore.pyd时,不会去搜索PATH中的Python3.dll,导致QtCore.pyd加载失败。

验证:

将Python3.dll拷贝到执行程序所在目录,调试执行,一切正常,问题解决。

附:python import调用过程,将使用importlib模块

importlib会调用内置模块"_imp",创建新的模块时会调用_imp.create_dynamic

create_dynamic最终会调用到_PyImport_FindSharedFuncptrWindows

你可能感兴趣的:(python)