使用Boost.Python暴露返回指针的C++函数
我想用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 个回答
简单来说,不能直接用 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 告诉我们,返回的对象必须是一个 class
或 union
,如果使用 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)