Cython - implementing callbacks

后端 未结 2 1901
予麋鹿
予麋鹿 2021-02-15 02:24

I have been working with Cython in an attempt to interface with a library written in c++. So far things are going pretty good, and I can effectively use MOST functions within th

相关标签:
2条回答
  • 2021-02-15 02:30

    If you can modify the library to define:

    typedef void (*Function1)(const uint16_t *data, 
                              unsigned width, unsigned height,
                              void *user_data);
    void SetCallBack(Function1, void*);
    

    instead, I fear you are out of luck. If you have the void*, than you define a function that calls a python callable object with correct arguments and SetCallBack with this function and the python callable.

    If you can't, but the callback is global (it seems to be), you can create a global variable to store the python object in. Than you'd again create a function to call the python object and pass it to SetCallBack and your PySetCallback would just set the global and ensure proper function is registered.

    If the callback is context-specific, but you have no way to pass it a "user data" pointer, I fear you are out of luck here.

    I know python and C++, but not cython, so I don't know whether you can create the function in cython, or whether you'd have to write in C++.

    0 讨论(0)
  • 2021-02-15 02:47

    I've recently been in the situation where I also had to interface an existing C++ library with Python using Cython, making an intensive use of events/callbacks. It was not that easy to find sources about this and I would like to put all of this together here :

    First of all, the wrapping C++ callback class (based on 'double (METHOD)(void)' prototype, but it could have been templatized, since Cython can handle templates) :

    ALabCallBack.h :

    #ifndef ALABCALLBACK_H_
    #define ALABCALLBACK_H_
    
    
    #include <iostream>
    
    using namespace std;
    
    namespace elps {
    
    //template < typename ReturnType, typename Parameter >
    class ALabCallBack {
    public:
    
        typedef double (*Method)(void *param, void *user_data);
    
        ALabCallBack();
        ALabCallBack(Method method, void *user_data);
        virtual ~ALabCallBack();
    
        double cy_execute(void *parameter);
    
        bool IsCythonCall()
        {
            return is_cy_call;
        }
    
    protected:
    
        bool is_cy_call;
    
    private:
    
        //void *_param;
        Method _method;
        void *_user_data;
    
    };
    
    
    } /* namespace elps */
    #endif /* ALABCALLBACK_H_ */
    

    ALabCallBack.cpp :

    #include "ALabCallBack.h"
    
    namespace elps {
    
    
    ALabCallBack::ALabCallBack() {
        is_cy_call = true;
    };
    
    ALabCallBack::~ALabCallBack() {
    };
    
    ALabCallBack::ALabCallBack(Method method, void *user_data) {
        is_cy_call = true;
        _method = method;
        _user_data = user_data;
    };
    
    double ALabCallBack::cy_execute(void *parameter)
    {
        return _method(parameter, _user_data);
    };
    
    
    } /* namespace elps */
    

    Where :

    • 'callback' :: The pattern/converter method to fire a Python (=Method) object method from C typed infos

    • 'method' :: The effective method passed by the Python user (=user_data)

    • 'parameter' :: The parameter to be passed to the 'method'

    Now, we need to implement the .pyx file...

    Our base prototype :

    ctypedef double (*Method)(void *param, void *user_data)
    

    Then, we provide a Cython wrapper for the C++ class :

    cdef extern from "../inc/ALabCallBack.h" namespace "elps" :
        cdef cppclass ALabCallBack:
            ALabCallBack(Method method, void *user_data)
            double cy_execute(void *parameter)
    

    The pattern/converter method to be used for translating C typed prototype to a Python object call :

    cdef double callback(void *parameter, void *method):
        return (<object>method)(<object>parameter)
    

    Now let's embed this features in a Cython class :

    cdef class PyLabCallBack:
        cdef ALabCallBack* thisptr
    
        def __cinit__(self, method):
            # 'callback' :: The pattern/converter method to fire a Python 
            #               object method from C typed infos
            # 'method'   :: The effective method passed by the Python user 
           self.thisptr = new ALabCallBack(callback, <void*>method)
    
        def __dealloc__(self):
           if self.thisptr:
               del self.thisptr
    
        cpdef double execute(self, parameter):
            # 'parameter' :: The parameter to be passed to the 'method'
            return self.thisptr.cy_execute(<void*>parameter)
    

    Edit : Better typing for execute function : def execute => cpdef double

    That's it. Call it like doing something like that :

    def func(obj):
        print obj 
        obj.Test()     # Call to a specific method from class 'PyLabNode'
        return obj.d_prop
    
    n = PyLabNode()    # Custom class of my own
    cb = PyLabCallBack(func)
    print cb.execute(n)
    

    As python is implicitly typed, we can access the properties of the 'obj' object related to the class of the object passed as argument when the time comes to fire the callback.

    It can be quite easily adapted for pure C implementation. Please, tell me if you can see any possible enhancement for this (in my case, perfs are very crucial since events are fired intensively).

    0 讨论(0)
提交回复
热议问题