""" PyInstaller 运行时钩子:固定 MSVC 运行库加载顺序,再注册 DLL 搜索路径。 打包阶段在 strip 掉各 wheel 自带 CRT 后,会 **仅嵌入** 构建所用 Python ``DLLs`` 下的 ``vcruntime140.dll`` / ``vcruntime140_1.dll`` / ``msvcp140.dll`` 至 ``_MEIPASS``。 本钩子在导入 chroma/torch 等扩展 **之前**,用 ``ctypes.WinDLL(绝对路径)`` 从 **同一目录** (优先 exe 旁若用户自行放置了完整三件套,否则 ``_MEIPASS``)预加载,避免进程落到 目标机 ``System32`` 里较旧版本(如 14.29)再与 native 扩展不兼容触发 ``0xc0000005``。 随后再 ``add_dll_directory`` exe 目录与 ``_MEIPASS``,并调整 ``PATH``。不使用 ``SetDllDirectoryW``。 """ def _preload_msvc_crt_once(): """若 exe 目录或 ``_MEIPASS`` 下存在 ``msvcp140.dll``,则按依赖顺序预加载该目录内 CRT。""" import ctypes import sys from pathlib import Path if sys.platform != "win32" or not getattr(sys, "frozen", False): return order = ("vcruntime140.dll", "vcruntime140_1.dll", "msvcp140.dll") search_dirs = [] try: exe_parent = Path(sys.executable).resolve().parent search_dirs.append(exe_parent) except OSError: pass meipass = getattr(sys, "_MEIPASS", None) if meipass: search_dirs.append(Path(meipass)) for d in search_dirs: if not d.is_dir(): continue if not (d / "msvcp140.dll").is_file(): continue for name in order: p = d / name if p.is_file(): try: ctypes.WinDLL(str(p)) except OSError: pass return def _apply_dll_search_paths(): """将 exe 目录与 ``_MEIPASS`` 注册为 DLL 目录,并置于 PATH 最前(exe 目录优先)。""" import os import sys from pathlib import Path if not getattr(sys, "frozen", False): return dirs = [] try: exe_dir = str(Path(sys.executable).resolve().parent) if exe_dir: dirs.append(exe_dir) except OSError: pass meipass = getattr(sys, "_MEIPASS", None) if meipass: dirs.append(meipass) for d in dirs: if not d: continue try: if hasattr(os, "add_dll_directory"): os.add_dll_directory(d) except OSError: pass path = os.environ.get("PATH", "") prefix = os.pathsep.join([d for d in dirs if d]) if not prefix: return if path.startswith(prefix): return os.environ["PATH"] = f"{prefix}{os.pathsep}{path}" _preload_msvc_crt_once() _apply_dll_search_paths()