pybind11 学习笔记
- 0. 一个例子
- 1. 官方文档
- 1.1 Installing the Library
- 1.1.1 Include as A Submodule
- 1.1.2 Include with PyPI
- 1.1.3 Include with Conda-forge
- 1.2 First Steps
- 1.2.1 Separate Files
- 1.2.2 `PYBIND11_MODULE()` 宏
- 1.2.3 example.`cpython-38-x86_64-linux-gnu`.so 的名称由来
- 1.3 Build Systems
- 1.3.1 原始的调用方式
- 1.3.2 Modules with CMake
- 1.3.2.1 关于包名 package name
- 1.3.2.2 关于版本
- 1.3.2.3 Python 环境设置
- 1.3.2.4 静态链接与动态链接
- 1.3.3 其他方式 setuptools 等
- 1.3.3.1 find_package vs. add_subdirectory
0. 一个例子
如果直接去看官方文档的话, 不熟悉 C/C++ 的初学者可能会一头雾水, 因为它只描述了导入 pybind11 库的方法, 且比较分散(让你点击链接到另一个网页查看), 没有展示一个系统的例子.
故而去 B 站搜索相关演示视频, 找到了《如何在Python中调用C++代码?pybind11极简教程》. 过程如下:
- 创建一个名为
VSPyBind11Test
的文件夹, 并在 VS Code 中打开, 为项目的根目录; - 在根目录下创建文件夹
extern
, 终端命令进入extern
, 执行命令:
git clone --recursive -b v2.12 --single-branch https://github.com/pybind/pybind11.git
- 下载好后,
extern
下有一个pybind11
文件夹, 就是pybind11
库; - 创建
CMakeLists.txt
和example.cpp
文件, 写入内容:
cmake_minimum_required(VERSION 3.10)
project(VSPyBind11Test)# 可能会需要, 如果配置了环境变量就不需要了
# 或者你想在特定 Python 环境下的编译(还是比较重要的)
# set(PYTHON_EXECUTABLE /root/Miniconda/bin/python)
# set(PYTHON_INCLUDE_DIRS /root/Miniconda/include/python3.8)add_subdirectory(extern/pybind11)
pybind11_add_module(example example.cpp)
#include <pybind11/pybind11.h>
namespace py = pybind11;int add(int a, int b)
{return a + b;
}PYBIND11_MODULE(example, m)
{m.doc() = "Example module";m.def("add", &add, "Add two integers");
}
- 在根目录下创建
build
文件夹, 并在终端进入, 执行命令cmake ..
, 然后make
, 就会生成一个叫example.cpython-38-x86_64-linux-gnu.so
的文件, 就是导出的可供 python 调用的包; - 在
build
目录下, 创建demo.py
, 写入import example; example.add(1, 2)
, 执行python demo.py
, 则输出3
.
初步注解:
add_subdirectory(extern/pybind11)
可能是 C/C++ 开发导入第三方库的一种方法, 有了它就可以在 C/C++ 项目中使用pybind11
的内容了;PYBIND11_MODULE(example, m)
中,example
是导出的 python 模块名,m
是模块引用, 用来定义模块内容;- 从生成的
example.cpython-38-x86_64-linux-gnu.so
命名中可以看到, 它是依赖于 python 环境的, 我的默认 python 确实是 python3.8. 如果在CMakeLists.txt
中设置 python 环境为 python3.6:
set(PYTHON_EXECUTABLE /root/Miniconda/envs/py36/bin/python)
set(PYTHON_INCLUDE_DIRS /root/Miniconda/envs/py36/include/python3.6)
则得到的文件是 example.cpython-36m-x86_64-linux-gnu.so
.
- 注意, 在 python3.6 下生成的
so
文件不能在 python3.8 下运行, 反之亦然.
在 VS Code 下, 即使你构建了项目, 点开
example.cpp
, 可能依然是这样的
重启一下 VS Code 就好了. 而 CLion 下只需要点击 Reload CMake Project 就好了.
1. 官方文档
之后, 再看官方文档会好很多.
1.1 Installing the Library
从 Library 得知, 它就是个库. 官方推荐的安装方式有三种: submodule, PyPI, conda-forge.
1.1.1 Include as A Submodule
就是上面例子中 CMakeLists.txt 文件中的 add_subdirectory(extern/pybind11)
. 之所以说官方文档不够清楚, 当看到:
时, 试了一下:
# add_subdirectory(extern/pybind11)
include_directories(/extern/ptbind11/include)
根本不行: Unknown CMake command "pybind11_add_module".
点击 see Build systems, 看到一份 CMake 代码:
cmake_minimum_required(VERSION 3.15...3.29)
project(example LANGUAGES CXX)set(PYBIND11_FINDPYTHON ON)
find_package(pybind11 CONFIG REQUIRED)pybind11_add_module(example example.cpp)
install(TARGETS example DESTINATION .)
把上面例子中的 CMakeLists.txt 文件内容换成这样, 会报错:
Could not find a package configuration file provided by "pybind11" with any of the following names:pybind11Config.cmakepybind11-config.cmake
即使在 find_package(...)
中写明路径:
find_package(pybind11 CONFIG REQUIRED PATHS /root/CuProjects/VSPyBind11Test/extern/pybind11)
[小结]: 对于 Include as A Submodule, 暂时只能用 add_subdirectory(extern/pybind11)
了.
1.1.2 Include with PyPI
文档只告诉你可以通过 pip 安装 pybind11:
pip install pybind11
说这是一个标准的 Python 包格式: This will provide pybind11 in a standard Python package format. 然后, 如果 you want pybind11 available directly in your environment root, you can use:
pip install "pybind11[global]"
这种安装方式会往 /usr/local/include/pybind11 和 /usr/local/share/cmake/pybind11 中添加文件, 所以官方不推荐这种安装方式. 也就是说, 这样安装 pybind11 可以让你在项目的根目录中直接使用它.
[注意] 最新的 pybind11-v2.13 要求 python >=3.7, 如果是老版本的 python, 请安装 v2.12.
??? 然后呢? 执行完 pip 安装后, 我该如何在 C 项目中导入 pybind11 呢? 文档再一次导向 Build systems. 既然已经安装了, 我们删除 extern/pybind11
, 使用 find_package(pybind11 CONFIG REQUIRED)
, 继续用所给的 CMake 代码, 执行 cmake ..
命令, 和上面一样, 报找不到错误. 那看看下面怎么说:
呃, 看不懂了, 不知道这个 pyproject.toml 是个啥文件, 从文件名和上下文看, 它应该是一个构建 Python 包所需要的配置文件, 为了确认它是什么, 以及它和上面的 CMakeLists.txt 文件是什么关系, 先问问通义千问: [pyproject.toml 是什么?]
pyproject.toml 是一个用于 Python 项目的配置文件,它主要用于存储与构建系统和依赖管理相关的元数据。这个文件格式最初是为了简化 Python 包的构建过程而引入的,现在已经被广泛接受,并成为 PEP 518 和 PEP 517 标准的一部分。这些标准定义了如何使用现代工具来构建 Python 包,而无需依赖传统的 setup.py 文件。
那么这样就清楚了, 它跟 pybind11 似乎关系不大, 这里可能就是为了说明如何构建 Python 包, 而不是说明如何在 C 项目中导入 pybind11. 继续往下面看, Modules with meson-python, Modules with setuptools, … 都在讲如何构建 Python 包. 那么至此, 难以找到 “pip install pybind11 后如何导入到 C 项目中” 的答案. 耗费了一天时间, 试了很多方案, 都没能解决.
不过功夫不负有心人, 迷迷糊糊试了一下:
find_package(pybind11 CONFIG REQUIRED PATHS /root/Miniconda/lib/python3.8/site-packages/pybind11/share/cmake/pybind11)
这个路径是前面报错中所说的找不到的 pybind11Config.cmake, pybind11-config.cmake
两个文件的路径, 而 /root/Miniconda/lib/python3.8/site-packages/pybind11
是 pybind11
的 pip 安装路径. 竟然成功地导入了. 可以正常地将 C 语言导出 Python 接口了.
于是想, 何必找那么细呢? 只给出 pybind11 的安装路径不行吗? 于是:
find_package(pybind11 CONFIG REQUIRED PATHS /root/Miniconda/lib/python3.8/site-packages/pybind11)
find_package(pybind11 CONFIG REQUIRED PATHS /root/Miniconda/lib/python3.8/site-packages)
都是可以的. 奇了怪了, 当初 find_package(pybind11 CONFIG REQUIRED PATHS extern/pybind11)
咋就不行呢? 后来还是被我发现了, 后面会讲.
1.1.3 Include with Conda-forge
虽然不太清楚 conda 安装和 pip 安装的具体区别, 但猜测这种方式和 pip 应该是很类似的:
conda install -c conda-forge pybind11
文档只给了这么多. 为了测试这种安装方式, 在执行 conda 安装之前先卸载掉 pip 安装的 pybind11:
pip uninstall pybind11
卸载掉之后的, 发现重新构建 CMake 项目就找不到 pybind11 了. conda 安装 pybind11 之后, 再试试 cmake ..
, 哎! 成功了! 重磅! 用 conda 安装的 pybind11, 不用指定路径, 只需要:
find_package(pybind11 CONFIG REQUIRED)
这可能是因为用 conda 安装时, /root/Miniconda/include
和 /root/Miniconda/share/cmake
目录下均出现了 pybind11
文件夹, 其中 /root/Miniconda/share/cmake/pybind11
中有那两个之前找不到的文件. 甚至, 我在 conda 的 bin 目录下发现了一个叫 pybind11-config
的文件, 在终端执行:
pybind11-config --version
能输出:
2.13.5
也就是说, conda 安装 pybind11 时配置好了路径. 怎么配置的? 终端执行:
pybind11-config -h
# >>>>>>>>>>>> output >>>>>>>>>>>>
usage: pybind11-config [-h] [--version] [--includes] [--cmakedir] [--pkgconfigdir]
optional arguments:-h, --help show this help message and exit--version Print the version and exit.--includes Include flags for both pybind11 and Python headers.--cmakedir Print the CMake module directory, ideal for setting -Dpybind11_ROOT in CMake.--pkgconfigdir Print the pkgconfig directory, ideal for setting $PKG_CONFIG_PATH.
pybind11-config --includes
-I/root/Miniconda/include/python3.8 -I/root/Miniconda/lib/python3.8/site-packages/pybind11/includepybind11-config --cmakedir
/root/Miniconda/lib/python3.8/site-packages/pybind11/share/cmake/pybind11pybind11-config --pkgconfigdir
/root/Miniconda/lib/python3.8/site-packages/pybind11/share/pkgconfig
配置原来都在这, 难怪能直接 find_package(...)
. 但是, 如果你在 CLion 中构建 CMake 项目, 似乎还是会报找不到 pybind11 的错误.
conda 不仅仅是一个 python 管理工具!!!
- 记得当初需要用 R 语言处理一些数据时, 就有人说能用 conda 安装 R 语言, 我尝试了一下, 还是不错的;
- 在打开
scikit-build-core
的网页时, 发现很多程序已经用scikit-build-core
构建并发布到 PyPI 上了, cmake 和 ninja 赫然在列, 能用 pip 安装, 那可能也能用 conda 安装, 终端执行conda install cmake
, 果然安装了 cmake-3.26.4, 虽然 PyPI 上已经 3.30.3 了.- 终端运行
cmake --version
, 输出3.26.4
; 执行conda deactivate
回到非 conda 环境, 输出3.10.2
, 是系统上的 cmake 版本; 在其他 conda 环境(如我的 py36)下, 也是3.10.2
. 所以, conda 安装的软件是依赖于当前环境的.[小结]: conda 环境更像是一个镶嵌在操作系统上的虚拟系统, 它有自己的 bin, include, lib, sbin, share, 安装软件时很像 apt; 它是独立的, 又能访问宿主系统.
有了上面的分析, 可能的原因就是我们在终端或者 VS Code 中构建 CMake 项目时, 用的是 conda 的 base 环境, 而 CLion 不是. 执行 conda deactivate
回到系统环境, 再执行 cmake ..
, 果然找不到 pybind11
了. 现在, 我的 base 环境下是有 cmake-3.26.4 的, 设置 CLion 的 cmake 为 /root/Miniconda/bin/cmake
, 果然又行得通了; 在系统的非 conda 终端中执行 /root/Miniconda/bin/cmake ..
也行得通. 验证了 conda 环境像是一个镶嵌在操作系统上的虚拟系统的说法.
至此, 安装和导入部分算是完成了!
1.2 First Steps
这一节没什么好说的, 基本和第 0 节的例子差不多. 值得一提的是:
也就是说, <pybind11/pybind11.h>
要写在第一行.
1.2.1 Separate Files
In practice, implementation and binding code will generally be located in separate files.
既然文档提了这一句, 那就试试看:
VSPyBind11Test/
|----CMakeLists.txt
|----build/
|----src/
| |----example.cpp
| |----add/
| | |----add.h
| | |----add.cpp
| |----sub/
| | |----sub.h
| | |----sub.cpp
#include <pybind11/pybind11.h>
#include "add.h"
#include "sub.h"namespace py = pybind11;PYBIND11_MODULE(example, m)
{m.doc() = "Example module";m.def("add", &add, "Add two integers");m.def("sub", &sub, "Sub two integers");
}
编辑 CMakeLists.txt:
cmake_minimum_required(VERSION 3.10...3.29)
project(example LANGUAGES CXX)include_directories(src/add src/sub)set(PYBIND11_FINDPYTHON ON)
find_package(pybind11 CONFIG REQUIRED)add_library(add SHARED src/add/add.cpp)
add_library(sub SHARED src/sub/sub.cpp)pybind11_add_module(example src/example.cpp)
target_link_libraries(example PRIVATE add sub)install(TARGETS example DESTINATION .)
则完成了多文件的链接编译. 这样的话, 如果需要导出现有的 C 函数, 只需要编写一个类似 example.cpp
的文件, 然后进行链接就可以了.
在这个 CMakeLists.txt 文件中, 你也可以这样写:
...
find_package(pybind11 CONFIG REQUIRED)
pybind11_add_module(example src/example.cpp src/add/add.cpp src/sub/sub.cpp)
install(TARGETS example DESTINATION .)
1.2.2 PYBIND11_MODULE()
宏
PYBIND11_MODULE(example, m)
{m.doc() = "Example module";m.def("add", &add, "Add two integers");m.def("sub", &sub, "Sub two integers");
}
上面代码中的 PYBIND11_MODULE()
是一个宏, 接收两个参数, example
是 module name
, 它大概应该和 example.cpp
一致, 试了一下 PYBIND11_MODULE(example1, m)
, 会发现 import example,
import example1
都报错. m
是一个指向 example
模块的 py::module_
类型的变量, 它是可变的, 如 mm
也可以.
关于此宏的其他功能, 可见官方文档, 这里就不多说了.
1.2.3 example.cpython-38-x86_64-linux-gnu
.so 的名称由来
文档中还提到:
$ c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix)
这是手动编译的命令, 执行 python-config --extension-suffix
, 会根据你的 python 解释器和系统得到:
.cpython-38-x86_64-linux-gnu.so
Python 解释器是可以设置的, CLion 中, 只要在设置中指定好解释器就好了. 当然也可以在 CMakeLists.txt 中直接设置:
set(PYTHON_EXECUTABLE /root/Miniconda/bin/python)
关于这一点, 上面的例子已经提过了.
1.3 Build Systems
当明白上面讲的内容后, 就会发现文档的这一节是在教你如何在使用 pybind11 导出 C 程序的 Python 接口的情况下, 构建 Python 包. 有多种方式, 包括 Modules with CMake, Modules with meson-python, Modules with setuptools, Building with cppimport, Building with CMake, …
(可以先了解一下 Python 包的构建, 前面关于 pyproject.toml
的事就清楚了.)
1.3.1 原始的调用方式
按理说, 像前面那样构建好 .so
文件就够了, 已经可以在 python 脚本中进行导入和调用:
import example
print(example.add(1, 2)) # 3
可移植性又好, 想在哪里调用就直接 cmake
一下. 因为在一个环境下编译的 .so
在另一个环境不一定可用.
不好的地方在于, 开发环境 IDE 会提示你找不到 example
, 因为这里没有指导 IDE 进行代码检查的 Python 代码信息. 解决办法是靠 example.pyi
, 它对代码的执行不产生任何影响, 只对 IDE 的代码检查及文档说明提供帮助:
def add(x: int, y: int) -> int:"""Add two integers:param x: The first integer:param y: The second integer:return: The result of x + y"""...def sub(x: int, y: int) -> int:"""Integer Subtraction Computation:param x: The first integer:param y: The second integer:return: The result of x - y"""...
这样, 不光 IDE 不报错, 还能提供漂亮的文档提示.
1.3.2 Modules with CMake
有了对 Python 包构建 的了解, 就彻底明白 pyproject.toml
是怎么回事:
[build-system] # 使用 scikit-build-core 后端构建 python 包
requires = ["scikit-build-core", "pybind11"]
build-backend = "scikit_build_core.build"[project]
name = "example"
version = "0.1.0"
但我现在只知道官方给的 Python 包构建的例子, 并不知道 scikit-build-core
作为后端时, 怎样将 CMake 项目构建成 Python 包. 于是, 跟着导航, 导向 scikit_build_example 看一看:
scikit_build_example/
├── LICENSE
├── README.md
├── CMakeLists.txt
├── pyproject.toml
├── src/
│ ├── main.cpp
│ └── scikit_build_example/
│ └── __init__.py
└── tests/
项目的目录结构(忽略了一些可选部分), 和 Packaging Python Projects 中给的例子差不多, 多了个 CMakeLists.txt
, example.py
换成了 main.cpp
. 具体内容就不列出来了, 请参考 Github. 同样是执行:
pip install .
# or
python -m build
就构建好了, 安装之后, 就可以正常使用了.
1.3.2.1 关于包名 package name
pyproject.toml
中的
[project]
# 生成的包名, pip 安装时使用的包名(xxx.dist-info), 但 import 时需要使用模块名(安装的代码所在地)
name = "example" # 本来是 scikit_build_example, 为了研究包名, 更改为 example
是指 pip 安装时的名称, 也就是 pip install .
后, 你如果要卸载, 就要用这个名字, 因为 site-packages
下, 包信息的文件夹是 example.dist-info
; 它也是你执行 python -m build
时, 生成的 whl
包的名字.
install(TARGETS _core DESTINATION cmake_example) # .so 文件所在地
这是 CMakeLists.txt
中的安装语句, 表示生成的 _core.so
文件会安装到 site-packages
文件夹下的哪里, 这里会安装到 cmake_example
文件夹下.
src/
├── main.cpp
└── scikit_build_example/ # 这个名字要跟 name = "..." 中的一致, 不然这个 __init__.py 不会被安装└── __init__.py
我估计是有了 name = "example"
之后, 构建工具会去 src
下寻找同名的 Python 包, 把它复制到 conda 环境的 site-packages
下. 而 C 包 xxx.so
是 Python 包所需要的, 所以, CMakeLists.txt 中的 DESTINATION
要考虑为: Python 包需要我在哪里!
1.3.2.2 关于版本
project(${SKBUILD_PROJECT_NAME}VERSION ${SKBUILD_PROJECT_VERSION}LANGUAGES CXX
)
其中的 SKBUILD_PROJECT_NAME
变量是
[project]
name = "example"
version = "0.0.1"
当你手动执行 cmake ..
时, 这两个值似乎都是空的, 当 python -m build
时, 应该是 scikit-build-core
执行的 cmake
命令, 并将 name = "example", version = "0.0.1"
传递给了 CMake.
CMake 获取版本信息后:
target_compile_definitions(_core PRIVATE VERSION_INFO=${PROJECT_VERSION})
中又有版本信息, 这应该是传递给 cpp 代码的, 因为我发现:
#ifdef VERSION_INFOm.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO);
#elsem.attr("__version__") = "dev";
#endif
在检测宏 VERSION_INFO
, 正是 CMake 中的 VERSION_INFO
, 把它删掉:
target_compile_definitions(_core PRIVATE)
执行 example.__version__
就输出 'dev'
. 设置成:
target_compile_definitions(_core PRIVATE VERSION_INFO=0.2.0)
执行 example.__version__
就输出 '0.2.0'
.
1.3.2.3 Python 环境设置
在 CMakeLists.txt 中, 可能使用以下语句设置 Python:
set(PYTHON_BASE_PATH /root/Miniconda/envs/xxx)
set(PYTHON_INCLUDE_DIRS ${PYTHON_BASE_PATH}/include/python3.8)
set(PYTHON_LIBRARY ${PYTHON_BASE_PATH}/lib/libpython3.8.so)
set(PYTHON_EXECUTABLE ${PYTHON_BASE_PATH}/bin/python)
但似乎这样更简单:
set(PYTHON_BASE_PATH /root/Miniconda/envs/xxx)
find_package(Python REQUIRED COMPONENTS Interpreter Development.Module PATHS ${PYTHON_BASE_PATH})# Interpreter 对应了
# set(PYTHON_EXECUTABLE ${PYTHON_BASE_PATH}/bin/python)
# 还能获取 Python_VERSION
# Development.Module 对应了
# set(PYTHON_INCLUDE_DIRS ${PYTHON_BASE_PATH}/include/python3.x)
# set(PYTHON_LIBRARY ${PYTHON_BASE_PATH}/lib/libpython3.x.so)
注意这个需要 CMake-3.12+(3.15+ recommended, 3.18.2+ ideal).
1.3.2.4 静态链接与动态链接
问题来了: 当按照 1.2.1 Separate Files 中的文件结构时, 安装 Python 包后就会报错:
ImportError: libadd.so: cannot open shared object file: No such file or directory
而 1.2.1 中的手动编译, 不打包 Python 包就不报错. 检查了一下安装后的包解构, 也没啥问题:
example/
├── __init__.py
├── _core.cpython-38-x86_64-linux-gnu.so
├── libadd.so
├── libsub.so
└── __pycache__/└── __init__.cpython-38.pyc
到底咋回事, cd 到包里面, 执行:
(base) root@kklt:~/CuProjects/CLionPyBind11Test# cd ~/Miniconda/lib/python3.8/site-packages/example/
(base) root@kklt:~/Miniconda/lib/python3.8/site-packages/example# ls
__init__.py __pycache__ _core.cpython-38-x86_64-linux-gnu.so libadd.so libsub.so
(base) root@kklt:~/Miniconda/lib/python3.8/site-packages/example# python
Python 3.8.18 (default, Sep 11 2023, 13:40:15)
[GCC 11.2.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import _core
Traceback (most recent call last):File "<stdin>", line 1, in <module>
ImportError: libadd.so: cannot open shared object file: No such file or directory
在包内调用也不行; 把自己编译的 _core.cpython-38-x86_64-linux-gnu.so
移动到 example 内:
(base) root@kklt:~/Miniconda/lib/python3.8/site-packages/example# cp ~/CuProjects/CLionPyBind11Test/cmake-build-debug/_core.cpython-38-x86_64-linux-gnu.so ./
(base) root@kklt:~/Miniconda/lib/python3.8/site-packages/example# python
Python 3.8.18 (default, Sep 11 2023, 13:40:15)
[GCC 11.2.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import _core
>>> _core.add(1, 2)
3
甚至 example
包也能正常导入了:
(base) root@kklt:~# python
Python 3.8.18 (default, Sep 11 2023, 13:40:15)
[GCC 11.2.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> example.add(1, 2)
3
不清楚什么原因, 可能是 Python 包构建工具经过了什么特殊处理导致的. 还有一种解决办法:
add_library(add STATIC src/add_module/add.cpp)
add_library(sub STATIC src/sub_module/sub.cpp)
SHARED
换成 STATIC
也可以, 此时甚至不需要 labadd.so
和 labadd.so
了. 问了通义千问:
对于 ImportError: libadd.so: cannot open shared object file: No such file or directory, 我把 add_library(add SHARED src/add/add.cpp) 改成 add_library(add STATIC src/add/add.cpp) 就解决了错误, 这是为什么?
你遇到的 ImportError: libadd.so: cannot open shared object file: No such file or directory 错误通常是因为 Python 在运行时找不到必要的共享库。当你将 add_library(add SHARED src/add/add.cpp) 改为 add_library(add STATIC src/add/add.cpp) 后,问题得到了解决,原因在于静态库和共享库在链接时的行为不同。
=> 共享库 (SHARED)
当使用 add_library(… SHARED …) 创建共享库时,生成的库文件(如 libadd.so)是一个独立的动态链接库。这意味着:
---- 动态链接:在编译时,链接器不会将库文件中的符号(函数和变量)嵌入到最终的可执行文件或库中。相反,它会在运行时查找这些符号。
---- 运行时依赖:运行时,Python 解释器需要找到并加载这个共享库。因此,共享库必须存在于 Python 解释器可以找到的地方(通常是 LD_LIBRARY_PATH 或 /etc/ld.so.conf 指定的路径)。
=> 静态库 (STATIC)
当使用 add_library(… STATIC …) 创建静态库时,生成的库文件(如 libadd.a)包含了所有必要的符号。这意味着:
---- 静态链接:在编译时,链接器会将静态库中的符号嵌入到最终的可执行文件或库中。这样做的结果是最终的可执行文件或库包含了静态库中的所有符号。
---- 无需运行时依赖:由于所有的符号都已经嵌入到了最终的可可执行文件或库中,因此在运行时不需要再查找和加载静态库。
但, 终究是不知道为啥构建的 Python 包会找不到就在跟前的 libadd.so
.
1.3.3 其他方式 setuptools 等
构建 Python 包的方式还有很多, 且似乎大有不同, 比如 setuptools
安装的包似乎找不到 .so
文件在哪, 且构建文件目录中竟然没有 CMakeLists.txt 问价, 故而不明白 C 项目是如何构建的.
内容太多且用不到, 只作为大概了解, 故而就不进一步追究了. 但在下文中还是看到了一点东西可以解答前面的疑惑:
1.3.3.1 find_package vs. add_subdirectory
对于基于 CMake 的 C 项目, 没有内部包含 pybind11 repository, 所以要进行 external installation, 再通过 find_package(pybind11)
查找.
find_package(pybind11 REQUIRED)
pybind11_add_module(example example.cpp)
注意: find_package(pybind11)
只有在 pybind11 被正确地安装到系统中后才能正确工作, 即, 下载或克隆后:
# Classic CMake
cd pybind11
mkdir build
cd build
cmake ..
make install
这是常规的安装软件的步骤, 所以, 前面说的"用 find_package(...)
替换 add_subdirectory
"不工作.
找到了 pybind11 包之后, 前面提到的 pybind11_add_module
就可以使用了. 当你定义了 PYBIND11_FINDPYTHON
:
set(PYBIND11_FINDPYTHON ON)
或者执行 cmake
命令行时加上: DPYBIND11_FINDPYTHON=ON
, 那么 pybind11 会自动为你执行 FindPython
操作.