From 9369563942d8de442508613191670123d5ae9d08 Mon Sep 17 00:00:00 2001 From: "lasean.zhou" Date: Wed, 15 Apr 2026 13:08:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0exe=E6=89=93=E5=8C=85?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packaging/pyi_rth_crt_path.py | 89 +++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 packaging/pyi_rth_crt_path.py diff --git a/packaging/pyi_rth_crt_path.py b/packaging/pyi_rth_crt_path.py new file mode 100644 index 0000000..cc104b4 --- /dev/null +++ b/packaging/pyi_rth_crt_path.py @@ -0,0 +1,89 @@ +""" +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()