pybind11 学习笔记

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极简教程》. 过程如下:

  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.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/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/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() 是一个宏, 接收两个参数, 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_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.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 操作.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/427095.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

常见 HTTP 状态码详解与Nginx 文件上传大小限制

在我们日常使用 Nginx 搭建网站或应用服务时&#xff0c;可能会遇到很多与文件上传和请求响应相关的问题。今天我们就来聊聊 如何限制文件上传的大小&#xff0c;并介绍一些常见的 HTTP 状态码 及其在 Nginx 中的处理方式。 一、文件上传大小限制 有时&#xff0c;我们需要限…

从入门到精通,玩转Python的print函数(探索Python print函数的隐藏功能)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 基础用法参数详解示例📝 高级用法自定义分隔符和结束符输出到文件追加模式📝 覆盖打印与进度条简单覆盖打印动态进度条示例代码⚓️ 相关链接 ⚓️📖 介绍 📖 刚开始学习编程时,我们接触到的第一个方…

【初阶数据结构】一文讲清楚 “堆” 和 “堆排序” -- 树和二叉树(二)(内含TOP-K问题)

文章目录 前言1. 堆1.1 堆的概念1.2 堆的分类 2. 堆的实现2.1 堆的结构体设置2.2 堆的初始化2.3 堆的销毁2.4 添加数据到堆2.4.1 "向上调整"算法 2.5 从堆中删除数据2.5.1 “向下调整”算法 2.6 堆的其它各种方法接口函数 3. 堆排序3.1 堆排序的代码实现 4. TOP-K问题…

微软Office全家桶再爆办公革命,o1模型加持重塑十亿人工作流!1句话生成PPT+自定义智能体

颠覆全球十亿打工人的Office办公全家桶&#xff0c;昨夜迎来重磅升级&#xff01; 在微软Copilot第二弹发布会上&#xff0c;CEO纳德拉官宣&#xff0c;「用AI构思&#xff0c;共同协作的全新工作流——WebWorkPages正式开启」。 全程半小时&#xff0c;每一幕都在透露着&…

GPT代码记录

#include <iostream>// 基类模板 template<typename T> class Base { public:void func() {std::cout << "Base function" << std::endl;} };// 特化的子类 template<typename T> class Derived : public Base<T> { public:void…

基于JDK1.8和Maven的GeoTools 28.X源码自主构建实践

目录 前言 一、GeoTools与Jdk的版本关系 1、GeoTools与Jdk版本 2、编译环境简介 二、使用Maven编译GeoTools28.X 1、GeoTools28.x 2、Maven的完整编译 3、构建时的问题 三、总结 前言 想要学习和掌握一个开源软件或者项目&#xff0c;源码是我们主要学习的内容。学习开…

JDBC笔记

文章目录 准备MySQL数据的建立和建表 idea 建工程和模块设置属性配置文件编写JDBC代码URL的设置JDBC 代码配置文件 准备MySQL 数据的建立和建表 idea 建工程和模块 设置属性配置文件 编写JDBC代码 URL的设置 JDBC 代码 package com.yanyu;import java.sql.*; import java.util…

vue2.0+ts注册全局函数和几个递归查找

vue2.0ts注册全局函数和几个递归查找 一、main.ts 一、main.ts // 定义你的全局函数,判断是否有按钮权限 interface Item {label: string;checked: number;[k: string]: any; } // 获取按钮时候权限 function globalLable(arr: Item[], label: string): boolean {for (const i…

硬件基础知识

驱动开发分为&#xff1a;裸机驱动、linux驱动 嵌入式&#xff1a;以计算机技术为基础&#xff0c;软硬结合的、可移植、可剪裁的专用计算机 单片机最小单元&#xff1a;vcc gnd reset 晶振 cpu --- soc :system on chip 片上外设 所有的程序都是在soc&#xff08;cpu&…

1.熟悉接口测试(Postman工具)

一、接口及其类型 API&#xff0c;应用编程接口&#xff0c;简称接口 通过接口&#xff0c;可以让程序和程序之间&#xff0c;能够互相交互。 接口分为两大类&#xff1a; 1&#xff09;基于TCP全双工&#xff08;适用于postman&#xff09; 2&#xff09;基于HTTP半双工 二、…

项目管理 | 一文读懂什么是敏捷开发管理

在快速变化的商业环境中&#xff0c;项目管理方式也在不断演进&#xff0c;其中敏捷开发管理因其高效、灵活和适应性强的特点&#xff0c;逐渐成为众多企业和团队的首选。本文将详细解析敏捷开发管理的定义、具体内容及其核心角色&#xff0c;帮助读者全面理解这一先进的项目管…

普罗米修斯监控

目录 概念 部署方法 1. 二进制&#xff08;源码包&#xff09; 2. 部署在k8s集群当中&#xff0c;用pod形式部署 概念 prometheus是开源的系统监控和告警。在k8s分布式的容器化管理系统当中&#xff0c;一般都是搭配prometheus来进行监控。它是服务监控系统&#xff0c;也…

git reflog 和 git log 的详解和区别

文章目录 1. git log 介绍基本用法&#xff1a;输出内容&#xff1a;常见选项&#xff1a;git log 的局限性&#xff1a; 2. git reflog 介绍基本用法&#xff1a;输出内容&#xff1a;git reflog 输出字段&#xff1a;常见选项&#xff1a;主要用途&#xff1a;示例&#xff1…

IP协议及相关特性

IP协议负责地址管理和路由选择。它的组成为&#xff1a; 接下来我们将对其中较重要的部分进行介绍。 4位版本&#xff1a;这里的四位版本只有两个取值 分别为IPv4和IPv6&#xff0c;这两个额分别为不同的IP协议&#xff0c;但是现在主流的还是IPv4但是近年来IPv6在中国的普及率…

【机器学习】OpenCV高级图像处理

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 OpenCV高级图像处理图像滤波线性滤波高斯滤波均值滤波双边滤波 非线性滤波中值滤…

移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——6.vector(模拟实现)

1.存储结构 https://cplusplus.com/reference/vector/vector/ namespace zone {template<class T> //需要模板class vector{public:private:iterator _start;iterator _finish;iterator _endofstorage;}; } 可见&#xff0c;vector内核是由三个指针实现的 2.默认成员函…

LabVIEW机械产品几何精度质检系统

随着制造业的发展&#xff0c;对产品质量的要求越来越高&#xff0c;机械产品的几何精度成为衡量其品质的重要指标。为了提高检测效率和精度&#xff0c;开发了一套基于LabVIEW的几何精度质检系统&#xff0c;该系统不仅可以自动化地进行几何尺寸的测量&#xff0c;而且能实时分…

高校宿舍电费管理怎么实现

1引言 在大学的象牙塔里&#xff0c;宿舍不仅是学子们休憩的港湾&#xff0c;更是青春记忆的重要载体。然而&#xff0c;随着科技的发展与生活习惯的改变&#xff0c;宿舍内的电器设备日益增多&#xff0c;电费管理成为了一个不可忽视的问题。本文将从高校宿舍电费管理的现状出…

ORA-28032 Your password has expired and the database is set to read only

做个记录。 non-cdb 处于只读状态&#xff0c;CDB创建到noncdb的dblink后产生的报错&#xff0c;dblink可以成功创建&#xff0c;但无法连接到non-cdb。 解决&#xff1a;一开始以为是cdb的密码不正确&#xff0c;mos上找到问题&#xff0c;non-cdb的密码过期了&#xff0c;并且…

【软件测试】测试的岗位有哪些?

求职入口有很多&#xff1a;相关企业官网、求职软件、校招、公众号等等。 下面就在某招聘网站上看看测试有哪些岗位吧&#xff01; 测试只是一个统称&#xff0c;在测试下面还有很多细分岗位。 但是测试的岗位主要分为以下俩个方面&#xff1a; 软件测试开发工程师&#xff…