> For the complete documentation index, see [llms.txt](https://chenxiaowei.gitbook.io/cmake-cookbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://chenxiaowei.gitbook.io/cmake-cookbook/9.0-chinese/9.5-chinese.md).

# 9.5 使用pybind11构建C++和Python项目

**NOTE**:*此示例代码可以在* <https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-9/recipe-05> *中找到，其中有一个C++示例。该示例在CMake 3.11版(或更高版本)中是有效的，并且已经在GNU/Linux、macOS和Windows上进行过测试。*

前面的示例中，我们使用Boost.Python与C(C++)接口。本示例中，我们将尝试使用pybind11将Python与C++接口。其实现利用了C++11的特性，因此需要支持C++11的编译器。我们将演示在配置时如何获取pybind11依赖和构建我们的项目，包括一个使用FetchContent方法的Python接口，我们在第4章第3节和第8章第4节中有过讨论。在第11章第2节时，会通过PyPI发布一个用CMake/pybind11构建的C++/Python项目。届时将重新讨论这个例子，并展示如何打包它，使它可以用pip安装。

## 准备工作

我们将保持`account.cpp`不变，只修改`account.cpp`:

```cpp
#pragma once
#include <pybind11/pybind11.h>
class Account
{
public:
  Account();
  ~Account();
  void deposit(const double amount);
  void withdraw(const double amount);
  double get_balance() const;

private:
  double balance;
};
namespace py = pybind11;
PYBIND11_MODULE(account, m)
{
  py::class_<Account>(m, "Account")
      .def(py::init())
      .def("deposit", &Account::deposit)
      .def("withdraw", &Account::withdraw)
      .def("get_balance", &Account::get_balance);
}
```

按照pybind11文档的方式，通过CMake构建(<https://pybind11.readthedocs.io/en/stable/compile> )。并使用`add_subdirectory`将pybind11导入项目。但是，不会将pybind11源代码显式地放到项目目录中，而是演示如何在配置时使用`FetchContent` (<https://cmake.org/cmake/help/v3.11/module/FetchContent.html> )。

为了在下一个示例中更好地重用代码，我们还将把所有源代码放到子目录中，并使用下面的项目布局:

```
.
├── account
│    ├── account.cpp
│    ├── account.hpp
│    ├── CMakeLists.txt
│    └── test.py
└── CMakeLists.txt
```

## 具体实施

让我们详细分析一下这个项目中，各个`CMakeLists.txt`文件的内容:

1. 主`CMakeLists.txt`文件:

   ```
   # define minimum cmake version
   cmake_minimum_required(VERSION 3.11 FATAL_ERROR)

   # project name and supported language
   project(recipe-05 LANGUAGES CXX)

   # require C++11
   set(CMAKE_CXX_STANDARD 11)
   set(CMAKE_CXX_EXTENSIONS OFF)
   set(CMAKE_CXX_STANDARD_REQUIRED ON)
   ```
2. 这个文件中，查询了用于测试的Python解释器:

   ```
   find_package(PythonInterp REQUIRED)
   ```
3. 然后，包含`account`子目录:

   ```
   add_subdirectory(account)
   ```
4. 定义单元测试:

   ```
   # turn on testing
   enable_testing()

   # define test
   add_test(
     NAME
       python_test
     COMMAND
       ${CMAKE_COMMAND} -E env ACCOUNT_MODULE_PATH=$<TARGET_FILE_DIR:account>
       ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/account/test.py
     )
   ```
5. `account/CMakeLists.txt`中，在配置时获取pybind11的源码：

   ```
   include(FetchContent)

   FetchContent_Declare(
     pybind11_sources
     GIT_REPOSITORY https://github.com/pybind/pybind11.git
     GIT_TAG v2.2
     )

   FetchContent_GetProperties(pybind11_sources)

   if(NOT pybind11_sources_POPULATED)
     FetchContent_Populate(pybind11_sources)

     add_subdirectory(
       ${pybind11_sources_SOURCE_DIR}
       ${pybind11_sources_BINARY_DIR}
       )
   endif()
   ```
6. 最后，定义Python模块。再次使用模块选项`add_library`。并将库目标的前缀和后缀属性设置为`PYTHON_MODULE_PREFIX`和`PYTHON_MODULE_EXTENSION`，这两个值由pybind11适当地推断出来:

   ```
   add_library(account
     MODULE
       account.cpp
     )

   target_link_libraries(account
     PUBLIC
       pybind11::module
     )

   set_target_properties(account
     PROPERTIES
       PREFIX "${PYTHON_MODULE_PREFIX}"
       SUFFIX "${PYTHON_MODULE_EXTENSION}"
     )
   ```
7. 进行测试：

   ```
   $ mkdir -p build
   $ cd build
   $ cmake ..
   $ cmake --build .
   $ ctest

   Start 1: python_test
   1/1 Test #1: python_test ...................... Passed 0.04 sec
   100% tests passed, 0 tests failed out of 1
   Total Test time (real) = 0.04 sec
   ```

## 工作原理

pybind11的功能和使用与Boost.Python非常类似。pybind11是一个更轻量级的依赖——不过需要编译器支持C++11。`account.hpp`中的接口定义与之前的示例非常类似:

```cpp
#include <pybind11/pybind11.h>
// ...
namespace py = pybind11;
PYBIND11_MODULE(account, m)
{
  py::class_<Account>(m, "Account")
      .def(py::init())
      .def("deposit", &Account::deposit)
      .def("withdraw", &Account::withdraw)
      .def("get_balance", &Account::get_balance);
}
```

同样，我们可以了解到Python方法是如何映射到C++函数的。解释`PYBIND11_MODULE`库是在导入的目标`pybind11::module`中定义，使用以下代码包括了这个模块:

```
add_subdirectory(
  ${pybind11_sources_SOURCE_DIR}
  ${pybind11_sources_BINARY_DIR}
  )
```

与之前的示例有两个不同之处:

* 不需要在系统上安装pybind11
* `${pybind11_sources_SOURCE_DIR}`子目录，包含pybind11的`CMakelist.txt`中，在我们开始构建项目时，这个目录并不存在

这个挑战的解决方案是用`FetchContent`，在配置时获取pybind11源代码和CMake模块，以便可以使用`add_subdirectory`引用。使用`FetchContent`模式，可以假设pybind11在构建树中可用，并允许构建和链接Python模块:

```
add_library(account
  MODULE
      account.cpp
  )

target_link_libraries(account
  PUBLIC
      pybind11::module
  )
```

使用下面的命令，确保Python模块库得到一个定义良好的前缀和后缀，并与Python环境兼容:

```
set_target_properties(account
  PROPERTIES
    PREFIX ${PYTHON_MODULE_PREFIX}
    SUFFIX ${PYTHON_MODULE_EXTENSION}
  )
```

主`CMakeLists.txt`文件的其余部分，都在执行测试(与前一个示例使用相同的`test.py`)。

## 更多信息

我们可以将pybind11源代码包含在项目源代码存储库中，这将简化CMake结构，并消除在编译时对pybind11源代码进行网络访问的要求。或者，我们可以将pybind11源路径定义为一个Git子模块(<https://git-scm.com/book/en/v2/Git-Tools-Submodules> )，以应对pybind11源依赖项的更新。

在示例中，我们使用`FetchContent`解决了这个问题，它提供了一种非常紧凑的方法来引用CMake子项目，而不是显式地跟踪它的源代码。同样，我们也可以使用超级构建的方法来解决这个问题(参见第8章)。

要查看如何简单函数、定义文档注释、映射内存缓冲区等进阶阅读，请参考pybind11文档:<https://pybind11.readthedocs.io>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://chenxiaowei.gitbook.io/cmake-cookbook/9.0-chinese/9.5-chinese.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
