从Python到C++的回调选项

luaexgnf  于 2021-09-08  发布在  Java
关注(0)|答案(1)|浏览(331)

你好,我试着用Cython调用C的一个Python用户定义的回调。但是,如果不在C侧或静态函数缓冲区进行更改,看起来是不可能的。那么,绑定propper回调(ctypes和cfunctype)是否只有一个选项?
赛顿0.29.23
a、 水电站:

typedef void (*Callback) ();

class A{
    Callback callback;
public:
    A(){
       this->callback = nullptr;
    }

    void set_callback(Callback callback){
        this->callback = callback;
    }

    void call_callback(){
        this->callback();
    }
};

a、 pxd:

cdef extern from "A.hpp":
    ctypedef void (*Callback) ()
    cdef cppclass A:
        A() except +

        void set_callback(Callback callback)

        void call_callback()

b、 pyx

from A cimport A, Callback

cdef class B:
    cdef A *c_self
    cdef object callback

    def __cinit__(self):
        self.c_self = new A()

    def __dealloc__(self):
        del self.c_self

    cdef void callback_func(self) with gil:
        print("I'm here")
        self.callback()

    def set_callback(self, callback):
        self.callback = callback
        self.c_self.set_callback(<Callback>self.callback_func)

    def call_callback(self):
        self.c_self.call_callback()

def print_():
    print("hello")

b = B()
b.set_callback(print)
b.call_callback()

输出:

I'm here
[segmentation fault]

看起来像ctypes:获取c函数的实际地址是一个很好的解决方法,但它使用ctypes。这让我很害怕,但很管用:
b、 pyx

from A cimport A, Callback
import ctypes
from libc.stdint cimport uintptr_t

cdef class B:
    cdef A *c_self
    cdef object callback

    def __cinit__(self):
        self.c_self = new A()

    def __dealloc__(self):
        del self.c_self

    def set_callback(self, callback):
        f = ctypes.CFUNCTYPE(None)(callback)
        self.callback = f
        cdef Callback c_callback = (<Callback*><uintptr_t>ctypes.addressof(f))[0]
        self.c_self.set_callback(c_callback)

    def call_callback(self):
        self.c_self.call_callback()

def hello():
    print("hello")

b = B()
b.set_callback(hello)
b.call_callback()
hivapdat

hivapdat1#

函数指针没有任何空间来存储额外信息。因此,在纯c语言中,不可能将可调用的python转换为函数指针。同样地 cdef a的功能 cdef class 必须存储示例的地址才能使用,这也是不可能的。
您有三种选择:
使用 ctypes 与在ctypes中一样:获取c函数的实际地址。 ctypes 通过运行时代码生成来实现这一点(因此只在它明确支持的处理器上工作)。
使用 std::function 在C中代替函数指针。它们可以存储任意信息。您需要编写一个对象 Package 器(或者重用其他地方的 Package 器),这样它就不完全是cython了。请参见从Cython到C的闭包。如何在本机回调中使用cython cdef类成员方法可能更好地介绍了您要做的事情。
使用c类方案,其中回调类型为

void (CallbackT)(/* any args */, void* user_data)

并在以下机构注册:

void register_callback(CallbackT func, void* user_data)

在这种情况下 user_data 您的地址是什么 B 示例(您需要确保 Py_INCREF 在设置地址和 Py_DECREF 在取消地址设置之前已取消设置)。带有类方法的cython回调提供了一个示例。

相关问题