Skip to content

Python 运行时与扩展 ​

形态:动态链接 + 随包运行时 ​

CPython 3.12.2 以动态库形式嵌入:python312.dll 与 Program.exe 放在同一个目录, 标准库和扩展模块放在旁边的 python312/ 目录里。

text
Vector/
├── Program.exe        ← 只链 python312.lib(导入库),本体约 9 MB
├── python312.dll      ← CPython 3.12.2 解释器
├── python3.dll        ← 稳定 ABI 转发层
├── vcruntime140.dll   ⎫
├── vcruntime140_1.dll ⎬ VC++ 运行时(随包提供,用户无需安装)
├── msvcp140.dll       ⎭
├── vanilla.mcp
└── python312/         ← PYTHONHOME
    ├── Lib/           ← 标准库
    │   └── site-packages/   ← 第三方包装这里
    └── DLLs/          ← 官方扩展模块(_ssl.pyd、_socket.pyd、libssl-3.dll …)

启动时 PythonRuntime.cpp 的 SetPythonHome() 把 PYTHONHOME 指向 <exe 所在目录>\python312,所以整个目录搬到任何位置都能直接跑。

链接方式由哪里决定 ​

位置内容
application/CMakeLists.txtfind_package(Python3 3.12 COMPONENTS Development REQUIRED)
同上target_link_libraries(Program PRIVATE Python3::Python)
同上target_compile_definitions(... Py_ENABLE_SHARED)
application/application.cpp顶部 #define Py_ENABLE_SHARED(必须在 #include <Python.h> 之前)

Py_ENABLE_SHARED 表示 Python API 走 __declspec(dllimport) —— 也就是动态嵌入。 它必须由 CMake 和源码两侧保持一致,否则同一个 TU 会取到不同的 pyconfig.h 分支。

构建前置

配置阶段需要本机存在 Python 3.12 的 Development 组件(头文件 + python312.lib)。 find_package(Python3 ... REQUIRED) 找不到会直接 configure 失败。

与「静态嵌入」的区别 ​

早期形态是把 CPython 核心编成静态库链进 exe(Python 2.7 时代就是那样)。 当前实现改成了动态嵌入,代价和收益都很直接:

静态嵌入当前:动态嵌入
python312.dll不需要需要,随包提供
Program.exe 体积~80 MB 以上~9 MB
官方 cp312-win_amd64 wheel❌ 不能加载✅ 可以直接用
.pyd 从哪导入符号从 Program.exe从 python312.dll
exe 能否改名不能(.pyd 按模块名绑定)✅ 可以
换 Python 版本要重编整个核心换 DLL + python312/ 即可
构建耗时要先编核心(几分钟)直接链接

第三方包:官方 wheel 可以直接装 ​

因为解释器本身就是标准的 python312.dll,带平台 tag 的 wheel 能正常加载:

bash
# 用系统里同版本的 Python 3.12 的 pip 装进运行目录
pip install numpy --target "Vector\python312\Lib\site-packages"
pip install Pillow requests --target "Vector\python312\Lib\site-packages"

纯 Python 包(py3-none-any)同样照此安装,或者直接把目录复制进 python312\Lib\site-packages\。

版本要对齐

cp312-…-win_amd64 里的 312 必须和嵌入的解释器一致(当前是 3.12)。 装完后插件里直接 import numpy 即可。

exe 里编进去的 19 个原生模块 ​

这些模块是 C++ 实现、编译进 Program.exe 的 pybind11 模块。启动阶段 PythonRuntime::initModules() 逐个调用它们的 PyInit_xxx(),把模块对象直接塞进 sys.modules,所以插件里 import engine 这类写法随时可用:

模块模块模块
enginenbtpackets
game_stateclient_instanceeasy_utils
_clienttan_game(tan lobby)_websocket
aes_chacha_raknet
utilitysettingmod_log
rotorfoppkt
api_errors

要新增一个原生模块,改 initModules() 并重编 exe;而纯 Python 插件不需要重编, 放进插件目录就会热重载。

运行库依赖 ​

Program.exe 按 /MD(cmake/Platform.cmake 里的 CMAKE_MSVC_RUNTIME_LIBRARY = MultiThreadedDLL)链接,因此需要 VC++ 2015-2022 运行时:

DLL说明
vcruntime140.dllC 运行时
vcruntime140_1.dll异常处理 / 栈展开(2019+ 编译器的产物)
msvcp140.dllC++ 标准库

发布包已把这三个 app-local 放在 exe 旁边,所以用户什么都不用装。

报「找不到 MSVCP140.dll」

说明运行目录里少了这几个 DLL。从完整发布包里补齐即可;或者让用户安装 Microsoft Visual C++ 2015-2022 Redistributable (x64)。

其余导入的系统 DLL(KERNEL32、WS2_32、CRYPT32、ADVAPI32、bcrypt、IPHLPAPI、 api-ms-win-crt-*)都是 Windows 自带;Windows 7 / 8.1 需要先装 KB2999226(UCRT)。

第三方库(OpenSSL、jsoncpp、libdatachannel、libwebsockets、SLikeNet、zlib)全部 静态链进 exe,不产生额外 DLL 依赖。

下一步 ​

第三方库请参阅各自目录下的许可证文件