我有一个大量回调驱动的C ++库。回调注册为std::function
个实例。
回调注册功能可能类似于:
void register(std::function<void(int)> callback);
我可以通过制作cdef
个对象来注册普通的libcpp.functional.function
函数。说我有这个回调:
cdef void my_callback(int n):
# do something interesting with n
我可以成功注册:
cdef function[void(int)]* f_my_callback = new function[void(int)](my_callback)
register(deref(f_my_callback))
问题是我想注册cdef class
方法 作为回调。我们说我有一个班级:
cdef class MyClass:
cdef function[void(int)]* f_callback
py_callback = lambda x: None
# some more init/implementation
def __init__(self):
# ** this is where the problem is **
self.f_callback = new function[void(int)](self.member_callback)
register(deref(self.f_callback))
cdef void member_callback(self, int n) with gil:
self.py_callback(n)
def register_py(self, py_callback):
self.py_callback = py_callback
self.f_callback = new function[void(int)](self.member_callback)
行无法正常工作,因为function[T]
构造函数正在查看MyClass
参数(self
)。在常规python中,执行self.member_callback
基本上等同于将self
部分应用于MyClass.member_callback
,所以我认为这样会好,但事实并非如此。
我做了一个typedef:
ctypedef void (*member_type)(int)
然后,如果我演员,我可以让它编译:
self.f_callback = new function[void(int)](<member_type>self.member_callback)
但是当回调实际进入时,一切都被打破了。对self
执行任何操作都会导致段错误。
&#39;权利&#39;将cython类成员方法作为C / C ++回调传递的方法?
修改:
为了澄清,这个问题具体是关于将成员方法(即带self
作为第一个参数的函数)传递给C ++函数,期望类型为std::function
的参数。
如果我使用member_callback
装饰@staticmethod
并删除对self
的所有引用,则一切都按预期工作。不幸的是,该方法需要访问self
才能正确执行类实例的逻辑。
答案 0 :(得分:1)
std::function
可以采用一系列参数。在它最简单的形式中,它需要一个直接映射到其模板类型的函数指针,这就是Cython包装器真正设置为应对的全部内容。对于包装C ++成员函数,您通常使用std::mem_fun
或std::mem_fun_ref
来获取相关类实例的副本或引用(在现代C ++中,您也可以选择lambda)功能,但这真的只是给你相同的选项)。
将其转换为Python / Cython,您需要将PyObject*
保存到您正在调用其成员的对象,并由此自行负责处理引用计数。不幸的是,Cython目前无法为您生成包装器,因此您需要自己编写一个包装器。基本上你需要一个可调用的对象来处理引用计数。
在a previous answer中,我展示了两种创建这种包装器的方法(其中一种是手动的,第二种是使用Boost Python节省了工作量,它已经实现了非常相似的功能)。我在那里展示的方案将适用于匹配相关签名的任何Python可调用。虽然我使用标准的Python模块级函数来说明它,但它对于成员函数同样有效,因为instance.memberfunction
生成一个绑定成员函数 - 一个Python可调用的函数也可以。
对于您的问题,唯一的区别是您使用cdef
成员函数而不是def
成员函数。这看起来应该可以工作但不会(最近版本的Cython尝试自动转换为Python可调用但它失败并出现运行时错误)。您可以创建一个lambda函数作为一个非常简单的包装器。从速度的角度来看,这略微不理想,但具有使用现有代码的优势。
您需要对之前的答案进行的修改如下:
如果您尝试使用手册PyObjectWrapper
版本,请更改参数类型以匹配您的签名(即将int, string&
更改为int
)。你不必为升级版本做这件事。
void register(std::function<void(int)> callback);
的包装需要:
cdef extern from "whatever.hpp":
void register(PyObjWrapper)
# or
void register(bpo)
取决于您使用的版本。这对于Cython来说是关于签名的,但由于这两个对象都是可调用的C ++对象,因此它们可以由C ++编译器自动转换为std::function
。
将register
称为register(PyObjWrapper(lambda x: self.member_callback(x)))
或register(get_as_bpo(lambda x: self.member_callback(x)))
。
可以创建一个专门针对使用cdef
函数的更高效版本。它将基于一个与PyObjWrapper
非常相似的对象。但是,如果我已编写的代码能够正常工作,我不愿意在没有明确理由的情况下这样做。
答案 1 :(得分:0)
我相信@DavidW的答案对于问题的简化版本是正确的。
为了清晰起见,我提出了我的问题的简化版本,但据我所知,我的问题的真实版本需要稍微不同的方法。
具体来说,在我的简化版本中,我表示将使用int
调用回调。如果是这种情况,我可以想象提供一个python可调用(boost python或PyObjWrapper
版本。)
但是,回调实际上是使用std::shared_ptr<T>
调用的,其中T
是库中的模板化类。 cdef
回调需要对该实例执行某些操作,并最终调用python可调用。据我所知,没有办法用C ++对象调用python函数,如std::shared_ptr
。
@ DavidW的回复虽然很有帮助,但它让我找到了适用于这种情况的解决方案。我做了一个C ++实用程序函数:
// wrapper.hpp
#include <memory>
#include <functional>
#include "my_cpp_project.hpp"
template<typename T>
using cy_callback = void (*)(void*, std::shared_ptr<my_cpp_class<T>>);
template<typename T>
class function_wrapper {
public:
static
std::function<void(std::shared_ptr<my_cpp_class<T>>)>
make_std_function(cy_callback<T> callback, void* c_self)
{
std::function<void(std::shared_ptr<my_cpp_class<T>>)>
wrapper = [=](std::shared_ptr<my_cpp_class<T>> sample) -> void
{
callback(c_self, sample);
};
return wrapper;
}
};
我用cython包裹的内容:
cdef extern from "wrapper.hpp":
cdef cppclass function_wrapper[T]:
ctypedef void (*cy_callback) (void*, shared_ptr[my_cpp_class[T]])
@staticmethod
function[void(shared_ptr[my_cpp_class[T]])] make_std_function(
cy_callback, void*)
基本上,我的想法是将cython类回调方法传递给包装器,并返回带有正确类型签名的std::function
版本。返回的函数实际上是一个lambda,它将self
参数应用于回调。
现在我可以制作我的cdef
成员方法并正确调用它们:
cdef void member_callback(self, shared_ptr[my_cpp_class[specific_type]] sample) with gil:
# do stuff with the sample ...
self.python_cb(sample_related_stuff)
注册看起来像:
register(function_wrapper[specific_type].make_std_function(<cb_type>self.member_callback, <void*>self))
注意那里的cb_type
施法者。包装器期待void*
,因此我们需要将其转换为匹配。这是typedef:
ctypedef void (*cb_type)(void*, shared_ptr[my_cpp_class[specific_type]])
希望这对类似船上的人有用。感谢@DavidW让我走上正轨。