Python 运行时与扩展
形态:动态链接 + 随包运行时
CPython 3.12.2 以动态库形式嵌入:python312.dll 与 Program.exe 放在同一个目录, 标准库和扩展模块放在旁边的 python312/ 目录里。
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.txt | find_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 能正常加载:
# 用系统里同版本的 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 这类写法随时可用:
| 模块 | 模块 | 模块 |
|---|---|---|
engine | nbt | packets |
game_state | client_instance | easy_utils |
_client | tan_game(tan lobby) | _websocket |
aes | _chacha | _raknet |
utility | setting | mod_log |
rotor | fop | pkt |
api_errors |
要新增一个原生模块,改 initModules() 并重编 exe;而纯 Python 插件不需要重编, 放进插件目录就会热重载。
运行库依赖
Program.exe 按 /MD(cmake/Platform.cmake 里的 CMAKE_MSVC_RUNTIME_LIBRARY = MultiThreadedDLL)链接,因此需要 VC++ 2015-2022 运行时:
| DLL | 说明 |
|---|---|
vcruntime140.dll | C 运行时 |
vcruntime140_1.dll | 异常处理 / 栈展开(2019+ 编译器的产物) |
msvcp140.dll | C++ 标准库 |
发布包已把这三个 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 依赖。
