从 python 到 c++ 的回调选项

Options for callback from python to c++

你好,一段时间以来,我一直在尝试使用 cython 从 c++ 调用 python 用户定义的回调。但看起来如果不在 c++ 端或静态函数缓冲区进行更改是不可能的。 那么,是否只有一个选项可以绑定适当的回调(带有 CFUNCTYPE 的 ctypes)?

Cython 0.29.23

A.hpp:

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。 这让我害怕,但有效:

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()

函数指针没有任何space来存储额外的信息。因此,不可能将 Python 可调用对象转换为纯 C 中的函数指针。类似地,cdef classcdef 函数必须存储可用实例的地址,即也不可能。

您有三个选择:

  1. 一样使用ctypes(答案的下半部分显示了如何做)。 ctypes 通过运行时代码生成实现这一点(因此仅适用于它明确支持的处理器)。

  2. 在 C++ 中使用 std::function 而不是函数指针。这些可以存储任意信息。您需要编写一个对象包装器(或从其他地方重新使用一个),这样它就不是完全纯的 Cython。参见 . What you're trying to do is probably better covered by

  3. 使用 class C 方案,回调类型为

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

    并注册于:

     void register_callback(CallbackT func, void* user_data)
    

    在这种情况下,user_data 将是您的 B 实例的地址(并且您需要确保在设置地址和 Py_DECREFed 在取消设置地址之前)。 提供了一个示例。