pybind11 学习笔记

0. 一个例子

如果直接去看官方文档的话, 不熟悉 C/C++ 的初学者可能会一头雾水, 因为它只描述了导入 pybind11 库的方法, 且比较分散(让你点击链接到另一个网页查看), 没有展示一个系统的例子.

故而去 B 站搜索相关演示视频, 找到了《如何在Python中调用C++代码?pybind11极简教程》. 过程如下:

  1. 创建一个名为 VSPyBind11Test 的文件夹, 并在 VS Code 中打开, 为项目的根目录;
  2. 在根目录下创建文件夹 extern, 终端命令进入 extern, 执行命令:
git clone --recursive -b v2.12 --single-branch https://github.com/pybind/pybind11.git
  1. 下载好后, extern 下有一个 pybind11 文件夹, 就是 pybind11 库;
  2. 创建 CMakeLists.txtexample.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");
}
  1. 在根目录下创建 build 文件夹, 并在终端进入, 执行命令 cmake .., 然后 make, 就会生成一个叫 example.cpython-38-x86_64-linux-gnu.so 的文件, 就是导出的可供 python 调用的包;
  2. 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.cmake
    pybind11-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/pybind11pybind11 的 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/include

pybind11-config --cmakedir
/root/Miniconda/lib/python3.8/site-packages/pybind11/share/cmake/pybind11

pybind11-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() 是一个宏, 接收两个参数, examplemodule 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_INFO
    m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO);
#else
    m.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.solabadd.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 操作.

pybind11是一个用于Python和C之间接口转换的轻量级库,它可以为现有的C代码创建Python接口绑定。 在pybind11中,绑定重载指的是支持在Python中使用相同名称但参数类型和数量不同的多个C++函数。这意味着可以根据传递给函数的参数类型和数量来自动选择正确的C++函数进行调用。 例如,可以使用pybind11的`def`函数来绑定重载的函数,如下所示: ```cpp #include <pybind11/pybind11.h> void foo(int x) { // do something } void foo(double x) { // do something else } namespace py = pybind11; PYBIND11_MODULE(example, m) { m.def("foo", py::overload_cast<int>(&foo)); m.def("foo", py::overload_cast<double>(&foo)); } ``` 在上面的代码中,`def`函数被用来绑定两个重载的函数`foo`,分别接受`int`和`double`类型的参数。当在Python中调用`foo`函数时,根据传递给函数的参数类型自动选择正确的C++函数进行调用。 总结起来,pybind11可以通过使用`def`函数来绑定多个名称相同但参数类型和数量不同的C++函数,从而实现函数的重载。 <span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [pybind11使用教程笔记__2_C++函数重载](https://blog.csdn.net/weixin_41521681/article/details/106200017)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [pybind11.tar](https://download.csdn.net/download/TracelessLe/13204469)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [pybind11学习 | 类的绑定](https://blog.csdn.net/qq_39784672/article/details/128427222)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值