返回介绍

9.4 使用 Boost.Python 构建 C++和 Python 项目

发布于 2025-05-06 21:45:59 字数 7081 浏览 0 评论 0 收藏

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

Boost 库为 C++代码提供了 Python 接口。本示例将展示如何在依赖于 Boost 的 C++项目中使用 CMake,之后将其作为 Python 模块发布。我们将重用前面的示例,并尝试用 Cython 示例中的 C++实现( account.cpp ) 进行交互。

准备工作

保持 account.cpp 不变的同时,修改前一个示例中的接口文件( account.hpp ):

#pragma once
​
#define BOOST_PYTHON_STATIC_LIB
#include <boost/python.hpp>
​
class Account
{
public:
  Account();
  ~Account();
  void deposit(const double amount);
  void withdraw(const double amount);
  double get_balance() const;
​
private:
  double balance;
};
​
namespace py = boost::python;
​
BOOST_PYTHON_MODULE(account)
{
  py::class_<Account>("Account")
      .def("deposit", &Account::deposit)
      .def("withdraw", &Account::withdraw)
      .def("get_balance", &Account::get_balance);
}

具体实施

如何在 C++项目中使用 Boost.Python 的步骤:

  1. 和之前一样,首先定义最低版本、项目名称、支持语言和默认构建类型:
    # define minimum cmake version
    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    ​
    # project name and supported language
    project(recipe-04 LANGUAGES CXX)
    ​
    # require C++11
    set(CMAKE_CXX_STANDARD 11)
    set(CMAKE_CXX_EXTENSIONS OFF)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    ​
    # we default to Release build type
    if(NOT CMAKE_BUILD_TYPE)
        set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
    endif()
  2. 本示例中,依赖 Python 和 Boost 库,以及使用 Python 进行测试。Boost.Python 组件依赖于 Boost 版本和 Python 版本,因此需要对这两个组件的名称进行检测:
    # for testing we will need the python interpreter
    find_package(PythonInterp REQUIRED)
    ​
    # we require python development headers
    find_package(PythonLibs ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} EXACT REQUIRED)
    ​
    # now search for the boost component
    # depending on the boost version it is called either python,
    # python2, python27, python3, python36, python37, ...
    ​
    list(
      APPEND _components
        python${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR}
        python${PYTHON_VERSION_MAJOR}
        python
      )
    ​
    set(_boost_component_found "")
    ​
    foreach(_component IN ITEMS ${_components})
      find_package(Boost COMPONENTS ${_component})
      if(Boost_FOUND)
          set(_boost_component_found ${_component})
          break()
      endif()
    endforeach()
    ​
    if(_boost_component_found STREQUAL "")
        message(FATAL_ERROR "No matching Boost.Python component found")
    endif()
  3. 使用以下命令,定义 Python 模块及其依赖项:
    # create python module
    add_library(account
      MODULE
          account.cpp
      )
    ​
    target_link_libraries(account
      PUBLIC
          Boost::${_boost_component_found}
      ${PYTHON_LIBRARIES}
      )
    ​
    target_include_directories(account
      PRIVATE
          ${PYTHON_INCLUDE_DIRS}
      )
    ​
    # prevent cmake from creating a "lib" prefix
    set_target_properties(account
      PROPERTIES
          PREFIX ""
      )
    ​
    if(WIN32)
      # python will not import dll but expects pyd
      set_target_properties(account
        PROPERTIES
            SUFFIX ".pyd"
      )
    endif()
  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}/test.py
      )
  5. 配置、编译和测试:
    $ mkdir -p build
    $ cd build
    $ cmake ..
    $ cmake --build .
    $ ctest
    ​
    Start 1: python_test
    1/1 Test #1: python_test ...................... Passed 0.10 sec
    100% tests passed, 0 tests failed out of 1
    Total Test time (real) = 0.11 sec

工作原理

现在,不依赖于 Cython 模块,而是依赖于在系统上的 Boost 库,以及 Python 的开发头文件和库。

Python 的开发头文件和库的搜索方法如下:

find_package(PythonInterp REQUIRED)
find_package(PythonLibs ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} EXACT REQUIRED)

首先搜索解释器,然后搜索开发头和库。此外,对 PythonLibs 的搜索要求开发头文件和库的主版本和次版本,与解释器的完全相同。但是,命令组合不能保证找到完全匹配的版本。

定位 Boost.Python 时,我们试图定位的组件的名称既依赖于 Boost 版本,也依赖于我们的 Python 环境。根据 Boost 版本的不同,可以调用 python、python2、python3、python27、python36、python37 等等。我们从特定的名称搜索到更通用的名称,已经解决了这个问题,只有在没有找到匹配的名称时才会失败:

list(
  APPEND _components
    python${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR}
    python${PYTHON_VERSION_MAJOR}
    python
  )
​
set(_boost_component_found "")
​
foreach(_component IN ITEMS ${_components})
    find_package(Boost COMPONENTS ${_component})
    if(Boost_FOUND)
        set(_boost_component_found ${_component})
        break()
    endif()
endforeach()
​
if(_boost_component_found STREQUAL "")
    message(FATAL_ERROR "No matching Boost.Python component found")
endif()

可以通过设置额外的 CMake 变量,来调整 Boost 库的使用方式。例如,CMake 提供了以下选项:

  • Boost_USE_STATIC_LIBS :设置为 ON 之后,可以使用静态版本的 Boost 库。
  • Boost_USE_MULTITHREADED :设置为 ON 之后,可以切换成多线程版本。
  • Boost_USE_STATIC_RUNTIME :设置为 ON 之后,可以在 C++运行时静态的连接不同版本的 Boost 库。

此示例的另一个特点是使用 add_library 的模块选项。我们已经从第 1 章第 3 节了解到,CMake 接受以下选项作为 add_library 的第二个有效参数:

  • STATIC :创建静态库,也就是对象文件的存档,用于链接其他目标时使用,例如:可执行文件
  • SHARED :创建共享库,也就是可以动态链接并在运行时加载的库
  • OBJECT :创建对象库,也就是对象文件不需要将它们归档到静态库中,也不需要将它们链接到共享对象中

MODULE 选项将生成一个插件库,也就是动态共享对象(DSO),没有动态链接到任何可执行文件,但是仍然可以在运行时加载。由于我们使用 C++来扩展 Python,所以 Python 解释器需要能够在运行时加载我们的库。使用 MODULE 选项进行 add_library ,可以避免系统在库名前添加前缀(例如:Unix 系统上的 lib)。后一项操作是通过设置适当的目标属性来执行的,如下所示:

set_target_properties(account
  PROPERTIES
      PREFIX ""
  )

完成 Python 和 C++接口的示例,需要向 Python 代码描述如何连接到 C++层,并列出对 Python 可见的符号,我们也有可能重新命名这些符号。在上一个示例中,我们在另一个单独的 account.pyx 文件这样用过。当使用 Boost.Python 时,我们直接用 C++代码描述接口,理想情况下接近期望的接口类或函数定义:

BOOST_PYTHON_MODULE(account) {
  py::class_<Account>("Account")
    .def("deposit", &Account::deposit)
    .def("withdraw", &Account::withdraw)
    .def("get_balance", &Account::get_balance);
}

BOOST_PYTHON_MODULE 模板包含在 <boost/python> 中,负责创建 Python 接口。该模块将公开一个 Account Python 类,该类映射到 C++类。这种情况下,我们不需要显式地声明构造函数和析构函数 - 编译器会有默认实现,并在创建 Python 对象时自动调用:

myaccount = Account()

当对象超出范围并被回收时,将调用析构函数。另外,观察 BOOST_PYTHON_MODULE 如何声明 depositwithdrawget_balance 函数,并将它们映射为相应的 C++类方法。

这样,Python 可以在 PYTHONPATH 中找到编译后的模块。这个示例中,我们实现了 Python 和 C++层之间相对干净的分离。Python 代码的功能不受限制,不需要类型注释或重写名称,并保持 Python 风格:

from account import Account
​
account1 = Account()
​
account1.deposit(100.0)
account1.deposit(100.0)
​
account2 = Account()
​
account2.deposit(200.0)
account2.deposit(200.0)
​
account1.withdraw(50.0)
​
assert account1.get_balance() == 150.0
assert account2.get_balance() == 400.0

更多信息

这个示例中,我们依赖于系统上安装的 Boost,因此 CMake 代码会尝试检测相应的库。或者,可以将 Boost 源与项目一起提供,并将此依赖项,作为项目的一部分构建。Boost 使用的是一种可移植的方式将 Python 与 C(++) 进行连接。然而,与编译器支持和 C++标准相关的可移植性是有代价的,因为 Boost.Python 不是轻量级依赖项。在接下来的示例中,我们将讨论 Boost.Python 的轻量级替代方案。

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。