Search code examples
cythoncythonize

Strange behaviour when creating python attributes in cython cdef class


We have given Cython code:

cdef extern from "C_File_A.h":
    cdef struct C_Obj_A:
        pass

cdef extern from "C_File_B.h":
    cdef struct C_Obj_B:
        pass

cdef class pC_Obj_A:
    cdef const C_Obj_A * _c_self

cdef class pC_Obj_B:
    cdef const C_Obj_B * _c_self

cdef class pC_Obj_C:
    cdef const C_Obj_A * _c_a
    cdef const C_Obj_B * _c_b


cdef class Obj_A_Wrap(pC_Obj_A):
    def __init__(self, pC_Obj_C obj_c):
        self._c_self = obj_c._c_a


cdef class Obj_B_Wrap(pC_Obj_B):
    def __init__(self, pC_Obj_C obj_c):
        self._c_self = obj_c._c_b


cdef class Stack:
    cdef public pC_Obj_A obj_a
    cdef public pC_Obj_B obj_b

    def __init__(self, pC_Obj_C obj_c):
        # Working
        self.obj_a = Obj_A_Wrap(obj_c)
        self.obj_b = Obj_B_Wrap(obj_c)

        # Working
        self.obj_a._c_self = obj_c._c_a
        self.obj_b = Obj_B_Wrap(obj_c)

        # Working
        self.obj_a = Obj_A_Wrap(obj_c)
        self.obj_b._c_self = obj_c._c_b

        # Not working
        self.obj_a._c_self = obj_c._c_a
        self.obj_b._c_self = obj_c._c_b

I need a python object Stack with attrubutes accessible from Python, so I have added to Stack class cdef public pC_Obj_A obj_a and cdef public pC_Obj_B obj_b.These objects are wrappers to the C struct pointers.

When I initialize these objects with intermediary wrappers i.e. Obj_A_Wrap everything is fine.

When I initialize one of these objects directly i.e. self.obj_a._c_self = obj_c._c_a also everything is fine.

When both obj_a and obj_b are initialized directly (# Not Working part of code) I have got strange behaviour of my C library that inlcude C_File_A and C_File_B and respectively the C structs definitions. The behaviour is similar to memory corruption, or overwriting some parts of the memory that should not be.

I have no idea why the direct initialization causes this strange behaviour. Maybe you know?


Solution

  • I have found the solution of my problem. When I was trying to solve this problem I have printed only _c_self attribute of the given object to check that the pointer was properly assigned and it was but when I printed entire object it turned out that python object is None instead of proper object declared as attribute.

    print(self.obj_a, self.obj_b) # 66f000c0 66f000c0
    print(f'{<int>self.obj_a._c_self:x} {<int>self.obj_b._c_self:x}') # None None
    

    The solution is to add Create function to cdef class:

    cdef class pC_Obj_A:
        cdef const C_Obj_A * _c_self
    
        @staticmethod
        cdef Create(C_Obj_A * ptr):
            cdef pC_Obj_A result = pC_Obj_A()
            result._c_self = ptr
            return result
    

    And use it like this:

    cdef class Stack:
        cdef public pC_Obj_A obj_a
        cdef public pC_Obj_B obj_b
    
        def __init__(self, pC_Obj_C obj_c):
            self.obj_a = pC_Obj_A.Create(obj_c._c_a)
            self.obj_b = pC_Obj_B.Create(obj_c._c_b)
    

    Then printout is:

    print(self.obj_a, self.obj_b) # <pC_Obj_A object at 0x029FF610> <pC_Obj_B object at 0x029FF620>
    print(f'{<int>self.obj_a._c_self:x} {<int>self.obj_b._c_self:x}') # 2134b9c 2134c08
    

    And everything works great!