Search code examples
pythonccythoncpython

Passing Cython class object as argument to C function


I am attempting to pass Cython instance class object as an argument to C function - so it can make a callback to its method.

This is what I have tried:

sample.c
#include "python.h"

void c_func(PyObject *obj){
    PyGILState_STATE gstate;
    gstate = PyGILState_Ensure();

    /* Dictionary Object - can be of any dynamic type */

    PyObject *dict_obj = PyDict_New();
    PyObject *key = PyUnicode_FromString("average-run");
    PyObject *val = PyLong_FromLong(45445);
    PyDict_SetItem(dict_obj, key, val);

    /* Make a callback */

    PyObject_CallMethod(obj, "func_a", "(d)", dict_obj);

    PyGILState_Release(gstate);
}
pysample.pxd
cdef extern from "sample.h":
    void c_func(object obj)

cdef class Test(object):
    cdef func_a(self, object dict_obj)
pysample.pyx
cimport pysample
from pysample cimport Test

cdef class Test(object):
    cdef func_a(self, object dict_obj):
        print(dict_obj)

    def run_test(self):
        # Here I make a call to the C function
        pysample.c_func(self)

Unfortunately the callback from C doesn't work. Can you spot what I am doing wrong or suggest a fix to this ?


Solution

  • The issue has been resolved. It was because the method in the class was defined as cdef instead of cpdef.

    sample.c
    #include "python.h"
    
    void c_func(PyObject *obj){
        PyGILState_STATE gstate;
        gstate = PyGILState_Ensure();
    
        /* Dictionary Object - can be of any dynamic type */
    
        PyObject *dict_obj = PyDict_New();
        PyObject *key = PyUnicode_FromString("average-run");
        PyObject *val = PyLong_FromLong(45445);
        PyDict_SetItem(dict_obj, key, val);
    
        /* Make a callback */
    
       PyObject *res = PyObject_CallMethod(obj, "func_a", "(O)", dict_obj);
    
       if( !res )
       {
           PyErr_Print();
       }else
       {
          Py_DECREF(res); 
       }
       /* 
        * Note: Do not remove reference counting
        * for callback argument - python will take
        * care of it when the callback python method
        * go out of scope. If you do - it will cause
        * inconsistent data behaviour at the callback 
        * method side.
        */
    
       PyGILState_Release(gstate);
    }
    
    pysample.pxd
    cdef extern from "sample.h":
        void c_func(object obj)
    
    cdef class Test(object):
        cpdef func_a(self, object dict_obj)
    
    pysample.pyx
    cimport pysample
    from pysample cimport Test
    
    cdef class Test(object):
        cpdef func_a(self, object dict_obj):
            print(dict_obj)
    
        def run_test(self):
            # Here I make a call to the C function
            pysample.c_func(self)