使用Boost.Python暴露返回指针的C++函数

13 投票
1 回答
8330 浏览
提问于 2025-04-18 12:05

我想用Boost.Python把下面这个C++函数暴露给Python:

int* test1() {
    return new int(42);
}

// Now exposing the function with Boost.Python

BOOST_PYTHON_MODULE(libtest1)
{
    using namespace boost::python;
    def("test1", test1);
}

当我尝试编译这个库时,出现了错误。我猜是因为Boost.Python不知道怎么把int*转换成PyObject。

我觉得需要做的是定义一个转换结构,像这样:

template<class T>
struct int_ptr_to_python
{
   static PyObject* convert(int* i_ptr)
   {
        return i_ptr;
   }
};

然后把它传递给BOOST_PYTHON_MODULE的声明:

BOOST_PYTHON_MODULE(libtest1)
{
    using namespace boost::python;
    def("test1", test1);
    to_python_converter<int*, int_ptr_to_python<int*> >();
}

但是这样也不行。我找不到关于如何处理返回指针的函数的任何信息。

有没有人能帮帮我?

1 个回答

19

简单来说,不能直接用 Boost.Python 把返回 int* 的函数暴露给 Python,因为在 Python 中没有对应的类型,因为整数是不可变的。

  • 如果你想把一个数字返回给 Python,那就直接返回 int 类型的值。这可能需要用一个辅助函数来适配旧的 API。
  • 如果你想返回一个用 new 表达式分配的对象的指针,那么这个对象必须是一个类或联合体,并且函数需要用特定的策略来说明谁负责管理这个对象。

我不想马上给出最终的解决方案,而是想先一步一步分析编译器的错误信息。在 Boost.Python 中,有时会用 C++11 之前的静态断言来提供编译器消息中的指示。不幸的是,在复杂的模板中找到这些信息可能会有点困难。

以下代码:

#include <boost/python.hpp>

int* make_int() { return new int(42); }

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::def("make_int", &make_int);
}

在 clang 中会产生以下相关输出,重要的细节用粗体强调:

.../boost/python/detail/caller.hpp:102:98: error:
  no member named 'get_pytype' in 'boost::python::detail::
    specify_a_return_value_policy_to_wrap_functions_returning<int*>'
  ...create_result_converter((PyObject*)0, (ResultConverter *)0, 
                             (ResultConverter *)0).g...

Boost.Python 告诉我们,对于返回 int* 的函数,需要指定一个 boost::python::return_value_policy。有多种 ResultConverterGenerators 模型。通常,这些策略用于控制返回对象的所有权或生命周期语义。由于原始代码中的函数直接返回一个新的指针给 Python,如果调用者需要负责删除这个对象,那么 boost::python::manage_new_object 是合适的选择。

即使指定了对象管理策略,仍然会失败:

#include <boost/python.hpp>

int* make_int() { return new int(42); }

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::def("make_int", &make_int,
    python::return_value_policy<python::manage_new_object>());
}

会产生以下相关输出:

.../boost/python/object/make_instance.hpp:27:9:
  error: no matching function for call to 'assertion_failed'
    BOOST_MPL_ASSERT((mpl::or_<is_class<T>, is_union<T> >));

在这种情况下,Boost.Python 告诉我们,返回的对象必须是一个 classunion,如果使用 managed_new_object 的 ResultConverterGenerator。对于 int*,最合适的解决方案是在通过 Boost.Python 层时返回 int 的值。不过,为了完整性,下面演示了:

  • 如何使用辅助函数来适配旧的 API。
  • 如何通过一个返回指针的工厂函数来限制用户自定义类型的创建。
#include <boost/python.hpp>

/// Legacy API.
int* make_int() { return new int(42); }

/// Auxiliary function that adapts the legacy API to Python.
int py_make_int()
{
  std::auto_ptr<int> ptr(make_int());
  return *ptr;
}

/// Auxiliary class that adapts the legacy API to Python.
class holder
  : private boost::noncopyable
{
public:
  holder()
    : value_(make_int())
  {}

  int get_value() const     { return *value_; }
  void set_value(int value) { *value_ = value; }

private:
  std::auto_ptr<int> value_;
};

/// Factory function for the holder class.
holder* make_holder()
{
  return new holder();
}

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::def("make_int", &py_make_int);

  python::class_<holder, boost::noncopyable>("Holder", python::no_init)
    .add_property("value",
      python::make_function(&holder::get_value),
      python::make_function(&holder::set_value))
    ;

  python::def("make_holder", &make_holder,
    python::return_value_policy<python::manage_new_object>());
}

交互使用:

>>> import example
>>> assert(42 == example.make_int())
>>> holder = example.Holder()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: This class cannot be instantiated from Python
>>> holder = example.make_holder()
>>> assert(42 == holder.value)
>>> holder.value *= 2
>>> assert(84 == holder.value)

撰写回答