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