import operator

from pypy.interpreter.baseobjspace import W_Root
from pypy.interpreter.error import OperationError, oefmt
from pypy.interpreter.gateway import interp2app
from pypy.interpreter.typedef import TypeDef, make_weakref_descr

from rpython.rlib import rgc
from rpython.rlib.objectmodel import keepalive_until_here, specialize
from rpython.rtyper.lltypesystem import lltype, rffi
from rpython.tool.sourcetools import func_with_new_name

from pypy.module._cffi_backend import misc


class W_CData(W_Root):
    _attrs_ = ['space', '_ptr', 'ctype', '_lifeline_']
    _immutable_fields_ = ['_ptr', 'ctype']
    _ptr = lltype.nullptr(rffi.CCHARP.TO)

    def __init__(self, space, ptr, ctype):
        from pypy.module._cffi_backend import ctypeobj
        assert lltype.typeOf(ptr) == rffi.CCHARP
        assert isinstance(ctype, ctypeobj.W_CType)
        self.space = space
        self._ptr = ptr    # don't access directly!  use "with cdata as ptr:"
        self.ctype = ctype

    def __enter__(self):
        """Use 'with cdata as ptr:' to access the raw memory.  It will
        stay alive at least until the end of the 'with' block.
        """
        return self._ptr

    def __exit__(self, *args):
        keepalive_until_here(self)

    def unsafe_escaping_ptr(self):
        """Generally unsafe: escape the pointer to raw memory.
        If 'self' is a subclass that frees the pointer in a destructor,
        it may be freed under your feet at any time.
        """
        return self._ptr

    def _repr_extra(self):
        with self as ptr:
            extra = self.ctype.extra_repr(ptr)
        return extra

    def _repr_extra_owning(self):
        from pypy.module._cffi_backend.ctypeptr import W_CTypePointer
        ctype = self.ctype
        if isinstance(ctype, W_CTypePointer):
            num_bytes = ctype.ctitem.size
        else:
            num_bytes = self._sizeof()
        return 'owning %d bytes' % num_bytes

    def repr(self):
        extra2 = self._repr_extra()
        extra1 = ''
        if not isinstance(self, W_CDataNewOwning):
            # it's slightly confusing to get "<cdata 'struct foo' 0x...>"
            # because the struct foo is not owned.  Trying to make it
            # clearer, write in this case "<cdata 'struct foo &' 0x...>".
            from pypy.module._cffi_backend import ctypestruct
            if isinstance(self.ctype, ctypestruct.W_CTypeStructOrUnion):
                extra1 = ' &'
        return self.space.newtext("<cdata '%s%s' %s>" % (
            self.ctype.name, extra1, extra2))

    def nonzero(self):
        with self as ptr:
            nonzero = self.ctype.nonzero(ptr)
        return self.space.newbool(nonzero)

    def int(self, space):
        with self as ptr:
            w_result = self.ctype.cast_to_int(ptr)
        return w_result

    def long(self, space):
        w_result = self.int(space)
        space = self.space
        if space.is_w(space.type(w_result), space.w_int):
            w_result = space.newlong(space.int_w(w_result))
        return w_result

    def float(self):
        with self as ptr:
            w_result = self.ctype.float(ptr)
        return w_result

    def complex(self):
        with self as ptr:
            w_result = self.ctype.complex(ptr)
        return w_result

    def len(self):
        from pypy.module._cffi_backend import ctypearray
        space = self.space
        if isinstance(self.ctype, ctypearray.W_CTypeArray):
            return space.newint(self.get_array_length())
        raise oefmt(space.w_TypeError,
                    "cdata of type '%s' has no len()", self.ctype.name)

    def _compare_mode(self, w_other):
        from pypy.module._cffi_backend.ctypeprim import W_CTypePrimitive
        zero = rffi.cast(lltype.Unsigned, 0)
        self_is_ptr = not isinstance(self.ctype, W_CTypePrimitive)
        other_is_ptr = (isinstance(w_other, W_CData) and
                           not isinstance(w_other.ctype, W_CTypePrimitive))
        if other_is_ptr and self_is_ptr:
            with self as ptr1, w_other as ptr2:
                ptr1 = rffi.cast(lltype.Unsigned, ptr1)
                ptr2 = rffi.cast(lltype.Unsigned, ptr2)
            return (0, ptr1, ptr2, None, None)
        elif other_is_ptr or self_is_ptr:
            return (-1, zero, zero, None, None)
        else:
            w_ob1 = self.convert_to_object()
            if isinstance(w_other, W_CData):
                w_ob2 = w_other.convert_to_object()
            else:
                w_ob2 = w_other
            return (1, zero, zero, w_ob1, w_ob2)

    def _make_comparison(name):
        op = getattr(operator, name)
        #
        def _cmp(self, w_other):
            space = self.space
            mode, adr1, adr2, w_ob1, w_ob2 = self._compare_mode(w_other)
            if mode == 0:
                return space.newbool(op(adr1, adr2))
            elif mode == 1:
                return getattr(space, name)(w_ob1, w_ob2)
            else:
                return space.w_NotImplemented
        #
        return func_with_new_name(_cmp, name)

    lt = _make_comparison('lt')
    le = _make_comparison('le')
    eq = _make_comparison('eq')
    ne = _make_comparison('ne')
    gt = _make_comparison('gt')
    ge = _make_comparison('ge')

    def hash(self):
        from pypy.module._cffi_backend.ctypeprim import W_CTypePrimitive
        if isinstance(self.ctype, W_CTypePrimitive):
            w_ob = self.convert_to_object()
            if not isinstance(w_ob, W_CData):
                return self.space.hash(w_ob)
        ptr = self.unsafe_escaping_ptr()
        h = rffi.cast(lltype.Signed, ptr)
        # To hash pointers in dictionaries.  Assumes that h shows some
        # alignment (to 4, 8, maybe 16 bytes), so we use the following
        # formula to avoid the trailing bits being always 0.
        h = h ^ (h >> 4)
        return self.space.newint(h)

    def getitem(self, w_index):
        space = self.space
        if space.isinstance_w(w_index, space.w_slice):
            w_o = self._do_getslice(w_index)
        else:
            i = space.getindex_w(w_index, space.w_IndexError)
            ctype = self.ctype._check_subscript_index(self, i)
            w_o = self._do_getitem(ctype, i)
        return w_o

    def _do_getitem(self, ctype, i):
        ctitem = ctype.ctitem
        with self as ptr:
            return ctitem.convert_to_object(
                rffi.ptradd(ptr, i * ctitem.size))

    def setitem(self, w_index, w_value):
        space = self.space
        if space.isinstance_w(w_index, space.w_slice):
            with self as ptr:
                self._do_setslice(w_index, w_value, ptr)
        else:
            i = space.getindex_w(w_index, space.w_IndexError)
            ctype = self.ctype._check_subscript_index(self, i)
            ctitem = ctype.ctitem
            with self as ptr:
                ctitem.convert_from_object(
                    rffi.ptradd(ptr, i * ctitem.size),
                    w_value)

    def _do_getslicearg(self, w_slice):
        from pypy.module._cffi_backend.ctypeptr import W_CTypePointer
        from pypy.objspace.std.sliceobject import W_SliceObject
        assert isinstance(w_slice, W_SliceObject)
        space = self.space
        #
        if space.is_w(w_slice.w_start, space.w_None):
            raise oefmt(space.w_IndexError, "slice start must be specified")
        start = space.int_w(w_slice.w_start)
        #
        if space.is_w(w_slice.w_stop, space.w_None):
            raise oefmt(space.w_IndexError, "slice stop must be specified")
        stop = space.int_w(w_slice.w_stop)
        #
        if not space.is_w(w_slice.w_step, space.w_None):
            raise oefmt(space.w_IndexError, "slice with step not supported")
        #
        if start > stop:
            raise oefmt(space.w_IndexError, "slice start > stop")
        #
        ctype = self.ctype._check_slice_index(self, start, stop)
        assert isinstance(ctype, W_CTypePointer)
        #
        return ctype, start, stop - start

    def _do_getslice(self, w_slice):
        ctptr, start, length = self._do_getslicearg(w_slice)
        #
        space = self.space
        ctarray = ctptr.cache_array_type
        if ctarray is None:
            from pypy.module._cffi_backend import newtype
            ctarray = newtype.new_array_type(space, ctptr, space.w_None)
            ctptr.cache_array_type = ctarray
        #
        ptr = self.unsafe_escaping_ptr()
        ptr = rffi.ptradd(ptr, start * ctarray.ctitem.size)
        return W_CDataSliced(space, ptr, ctarray, length)

    def _do_setslice(self, w_slice, w_value, ptr):
        ctptr, start, length = self._do_getslicearg(w_slice)
        ctitem = ctptr.ctitem
        ctitemsize = ctitem.size
        target = rffi.ptradd(ptr, start * ctitemsize)
        #
        if isinstance(w_value, W_CData):
            from pypy.module._cffi_backend import ctypearray
            ctv = w_value.ctype
            if (isinstance(ctv, ctypearray.W_CTypeArray) and
                ctv.ctitem is ctitem and
                w_value.get_array_length() == length):
                # fast path: copying from exactly the correct type
                with w_value as source:
                    source = rffi.cast(rffi.VOIDP, source)
                    target = rffi.cast(rffi.VOIDP, target)
                    size = rffi.cast(rffi.SIZE_T, ctitemsize * length)
                    rffi.c_memcpy(target, source, size)
                return
        #
        # A fast path for <char[]>[0:N] = "somestring" or some bytearray.
        from pypy.module._cffi_backend import ctypeprim
        space = self.space
        if isinstance(ctitem, ctypeprim.W_CTypePrimitive) and ctitem.size == 1:
            if space.isinstance_w(w_value, space.w_bytes):
                from rpython.rtyper.annlowlevel import llstr
                from rpython.rtyper.lltypesystem.rstr import copy_string_to_raw
                value = space.bytes_w(w_value)
                if len(value) != length:
                    raise oefmt(space.w_ValueError,
                                "need a string of length %d, got %d",
                                length, len(value))
                copy_string_to_raw(llstr(value), target, 0, length)
                return
            if space.isinstance_w(w_value, space.w_bytearray):
                value = w_value.bytearray_list_of_chars_w(space)
                if len(value) != length:
                    raise oefmt(space.w_ValueError,
                                "need a bytearray of length %d, got %d",
                                length, len(value))
                self._copy_list_of_chars_to_raw(value, target, length)
                return
        #
        self._do_setslice_iterate(space, ctitem, w_value, target, ctitemsize,
                                  length)

    @staticmethod
    def _do_setslice_iterate(space, ctitem, w_value, target, ctitemsize,
                             length):
        # general case, contains a loop
        # (XXX is it worth adding a jitdriver here?)
        w_iter = space.iter(w_value)
        for i in range(length):
            try:
                w_item = space.next(w_iter)
            except OperationError as e:
                if not e.match(space, space.w_StopIteration):
                    raise
                raise oefmt(space.w_ValueError,
                            "need %d values to unpack, got %d", length, i)
            ctitem.convert_from_object(target, w_item)
            target = rffi.ptradd(target, ctitemsize)
        try:
            space.next(w_iter)
        except OperationError as e:
            if not e.match(space, space.w_StopIteration):
                raise
        else:
            raise oefmt(space.w_ValueError,
                        "got more than %d values to unpack", length)

    @staticmethod
    def _copy_list_of_chars_to_raw(value, target, length):
        # contains a loop, moved out of _do_setslice()
        for i in range(length):
            target[i] = value[i]

    def _add_or_sub(self, w_other, sign):
        space = self.space
        i = sign * space.getindex_w(w_other, space.w_OverflowError)
        ptr = self.unsafe_escaping_ptr()
        return self.ctype.add(ptr, i)

    def add(self, w_other):
        return self._add_or_sub(w_other, +1)

    def sub(self, w_other):
        space = self.space
        if isinstance(w_other, W_CData):
            from pypy.module._cffi_backend import ctypeptr, ctypearray
            ct = w_other.ctype
            if isinstance(ct, ctypearray.W_CTypeArray):
                ct = ct.ctptr
            #
            if (ct is not self.ctype or
                   not isinstance(ct, ctypeptr.W_CTypePointer) or
                   (ct.ctitem.size <= 0 and not ct.is_void_ptr)):
                raise oefmt(space.w_TypeError,
                            "cannot subtract cdata '%s' and cdata '%s'",
                            self.ctype.name, ct.name)
            #
            itemsize = ct.ctitem.size
            with self as ptr1, w_other as ptr2:
                diff = (rffi.cast(lltype.Signed, ptr1) -
                        rffi.cast(lltype.Signed, ptr2))
            if itemsize > 1:
                if diff % itemsize:
                    raise oefmt(space.w_ValueError,
                        "pointer subtraction: the distance between the two "
                        "pointers is not a multiple of the item size")
                diff //= itemsize
            return space.newint(diff)
        #
        return self._add_or_sub(w_other, -1)

    def getcfield(self, w_attr, mode):
        space = self.space
        attr = space.text_w(w_attr)
        try:
            cfield = self.ctype.getcfield(attr)
        except KeyError:
            raise oefmt(space.w_AttributeError, "cdata '%s' has no field '%s'",
                        self.ctype.name, attr)
        if cfield is None:
            raise oefmt(space.w_AttributeError,
                        "cdata '%s' points to an opaque type: cannot %s fields",
                        self.ctype.name, mode)
        return cfield

    def getattr(self, w_attr):
        cfield = self.getcfield(w_attr, mode="read")
        with self as ptr:
            w_res = cfield.read(ptr, self)
        return w_res

    def setattr(self, w_attr, w_value):
        cfield = self.getcfield(w_attr, mode="write")
        with self as ptr:
            cfield.write(ptr, w_value)

    def call(self, args_w):
        with self as ptr:
            w_result = self.ctype.call(ptr, args_w)
        return w_result

    def iter(self):
        return self.ctype.iter(self)

    def unpackiterable_int(self, space):
        from pypy.module._cffi_backend import ctypearray
        ctype = self.ctype
        if isinstance(ctype, ctypearray.W_CTypeArray):
            length = self.get_array_length()
            with self as ptr:
                return ctype.ctitem.unpack_list_of_int_items(ptr, length)
        return None

    def unpackiterable_float(self, space):
        from pypy.module._cffi_backend import ctypearray
        ctype = self.ctype
        if isinstance(ctype, ctypearray.W_CTypeArray):
            length = self.get_array_length()
            with self as ptr:
                return ctype.ctitem.unpack_list_of_float_items(ptr, length)
        return None

    @specialize.argtype(1)
    def write_raw_signed_data(self, source):
        with self as ptr:
            misc.write_raw_signed_data(ptr, source, self.ctype.size)

    @specialize.argtype(1)
    def write_raw_unsigned_data(self, source):
        with self as ptr:
            misc.write_raw_unsigned_data(ptr, source, self.ctype.size)

    def write_raw_float_data(self, source):
        with self as ptr:
            misc.write_raw_float_data(ptr, source, self.ctype.size)

    def write_raw_complex_data(self, real, imag):
        with self as ptr:
            halfsize = self.ctype.size >> 1
            ptr2 = rffi.ptradd(ptr, halfsize)
            misc.write_raw_float_data(ptr, real, halfsize)
            misc.write_raw_float_data(ptr2, imag, halfsize)

    def convert_to_object(self):
        with self as ptr:
            w_obj = self.ctype.convert_to_object(ptr)
        return w_obj

    def get_array_length(self):
        from pypy.module._cffi_backend import ctypearray
        ctype = self.ctype
        assert isinstance(ctype, ctypearray.W_CTypeArray)
        length = ctype.length
        assert length >= 0
        return length

    def _sizeof(self):
        return self.ctype.size

    def with_gc(self, w_destructor, size=0):
        space = self.space
        if space.is_none(w_destructor):
            if isinstance(self, W_CDataGCP):
                self.detach_destructor()
                w_res = space.w_None
            else:
                raise oefmt(space.w_TypeError,
                            "Can remove destructor only on a object "
                            "previously returned by ffi.gc()")
        else:
            with self as ptr:
                w_res = W_CDataGCP(space, ptr, self.ctype, self, w_destructor)
        if size != 0:
            if isinstance(w_res, W_CDataGCP):
                rgc.add_memory_pressure(size, w_res)
            else:
                rgc.add_memory_pressure(size, self)
        return w_res

    def unpack(self, length):
        from pypy.module._cffi_backend.ctypeptr import W_CTypePtrOrArray
        space = self.space
        if not self.ctype.is_nonfunc_pointer_or_array:
            raise oefmt(space.w_TypeError,
                        "expected a pointer or array, got '%s'",
                        self.ctype.name)
        if length < 0:
            raise oefmt(space.w_ValueError, "'length' cannot be negative")
        ctype = self.ctype
        assert isinstance(ctype, W_CTypePtrOrArray)
        with self as ptr:
            if not ptr:
                raise oefmt(space.w_RuntimeError,
                            "cannot use unpack() on %R",
                            self)
            w_result = ctype.ctitem.unpack_ptr(ctype, ptr, length)
        return w_result

    def dir(self, space):
        from pypy.module._cffi_backend.ctypeptr import W_CTypePointer
        ct = self.ctype
        if isinstance(ct, W_CTypePointer):
            ct = ct.ctitem
        lst = ct.cdata_dir()
        return space.newlist([space.newtext(s) for s in lst])

    def get_structobj(self):
        return None

    def enter_exit(self, exit_now):
        raise oefmt(self.space.w_ValueError,
            "only 'cdata' object from ffi.new(), ffi.gc(), ffi.from_buffer() "
            "or ffi.new_allocator()() can be used with the 'with' keyword or "
            "ffi.release()")

    def descr_enter(self):
        self.enter_exit(False)
        return self

    def descr_exit(self, args_w):
        self.enter_exit(True)

    def get_maximum_buffer_size(self):
        return -1


class W_CDataMem(W_CData):
    """This is used only by the results of cffi.cast('int', x)
    or other primitive explicitly-casted types."""
    _attrs_ = []

    def __init__(self, space, ctype):
        cdata = lltype.malloc(rffi.CCHARP.TO, ctype.size, flavor='raw',
                              zero=False)
        W_CData.__init__(self, space, cdata, ctype)

    @rgc.must_be_light_finalizer
    def __del__(self):
        lltype.free(self._ptr, flavor='raw')


class W_CDataNewOwning(W_CData):
    """This is the abstract base class used for cdata objects created
    by newp().  They create and free their own memory according to an
    allocator."""

    # the 'allocated_length' is >= 0 for arrays; for var-sized
    # structures it is the total size in bytes; otherwise it is -1.
    _attrs_ = ['allocated_length']
    _immutable_fields_ = ['allocated_length']

    def __init__(self, space, cdata, ctype, length=-1):
        W_CData.__init__(self, space, cdata, ctype)
        self.allocated_length = length

    def _repr_extra(self):
        return self._repr_extra_owning()

    def _sizeof(self):
        ctype = self.ctype
        if self.allocated_length >= 0:
            from pypy.module._cffi_backend import ctypearray
            if isinstance(ctype, ctypearray.W_CTypeArray):
                return self.allocated_length * ctype.ctitem.size
            else:
                return self.allocated_length    # var-sized struct size
        else:
            return ctype.size

    def get_array_length(self):
        from pypy.module._cffi_backend import ctypearray
        assert isinstance(self.ctype, ctypearray.W_CTypeArray)
        return self.allocated_length

    def get_structobj(self):
        return self

    def enter_exit(self, exit_now):
        from pypy.module._cffi_backend.ctypeptr import W_CTypePtrOrArray
        if not isinstance(self.ctype, W_CTypePtrOrArray):
            W_CData.enter_exit(self, exit_now)
        elif exit_now:
            self._do_exit()

    def _do_exit(self):
        raise NotImplementedError


class W_CDataNewStd(W_CDataNewOwning):
    """Subclass using the standard allocator, lltype.malloc()/lltype.free()"""
    _attrs_ = ['datasize']    # changed to -1 after being explicitly freed

    def __init__(self, space, cdata, ctype, length, datasize):
        W_CDataNewOwning.__init__(self, space, cdata, ctype, length)
        assert datasize >= 0
        self.datasize = datasize

    @rgc.must_be_light_finalizer
    def __del__(self):
        if self.datasize >= 0:
            lltype.free(self._ptr, flavor='raw')

    def _do_exit(self):
        if self.datasize >= 0:
            rgc.add_memory_pressure(-self.datasize, self)
            self.datasize = -1
            rgc.may_ignore_finalizer(self)
            lltype.free(self._ptr, flavor='raw')

    def get_maximum_buffer_size(self):
        return self.datasize


class W_CDataNewNonStd(W_CDataNewOwning):
    """Subclass using a non-standard allocator"""
    _attrs_ = ['w_raw_cdata', 'w_free']

    def _finalize_(self):
        if self.w_free is not None:
            self.space.call_function(self.w_free, self.w_raw_cdata)

    def _do_exit(self):
        w_free = self.w_free
        if w_free is not None:
            rgc.add_memory_pressure(-self._sizeof(), self)
            self.w_free = None
            self.may_unregister_rpython_finalizer(self.space)
            self.space.call_function(w_free, self.w_raw_cdata)


class W_CDataPtrToStructOrUnion(W_CData):
    """This subclass is used for the pointer returned by new('struct foo *').
    It has a strong reference to a W_CDataNewOwning that really owns the
    struct, which is the object returned by the app-level expression 'p[0]'.
    But it is not itself owning any memory, although its repr says so;
    it is merely a co-owner."""
    _attrs_ = ['structobj']
    _immutable_fields_ = ['structobj']

    def __init__(self, space, cdata, ctype, structobj):
        W_CData.__init__(self, space, cdata, ctype)
        self.structobj = structobj

    def _repr_extra(self):
        return self.structobj._repr_extra_owning()

    def _do_getitem(self, ctype, i):
        assert i == 0
        return self.structobj

    def get_structobj(self):
        structobj = self.structobj
        if isinstance(structobj, W_CDataNewOwning):
            return structobj
        else:
            return None

    def enter_exit(self, exit_now):
        if exit_now:
            structobj = self.structobj
            if isinstance(structobj, W_CDataNewOwning):
                structobj._do_exit()

    def get_maximum_buffer_size(self):
        return self.structobj._sizeof()


class W_CDataSliced(W_CData):
    """Subclass with an explicit length, for slices."""
    _attrs_ = ['length']
    _immutable_fields_ = ['length']

    def __init__(self, space, cdata, ctype, length):
        W_CData.__init__(self, space, cdata, ctype)
        self.length = length

    def _repr_extra(self):
        return "sliced length %d" % (self.length,)

    def get_array_length(self):
        return self.length

    def _sizeof(self):
        from pypy.module._cffi_backend.ctypeptr import W_CTypePtrOrArray
        ctype = self.ctype
        assert isinstance(ctype, W_CTypePtrOrArray)
        return self.length * ctype.ctitem.size


class W_CDataHandle(W_CData):
    _attrs_ = ['w_keepalive']
    _immutable_fields_ = ['w_keepalive']

    def __init__(self, space, cdata, ctype, w_keepalive):
        W_CData.__init__(self, space, cdata, ctype)
        self.w_keepalive = w_keepalive

    def _repr_extra(self):
        w_repr = self.space.repr(self.w_keepalive)
        return "handle to %s" % (self.space.text_w(w_repr),)


class W_CDataFromBuffer(W_CData):
    _attrs_ = ['buf', 'length', 'w_keepalive']
    _immutable_fields_ = ['buf', 'length']

    def __init__(self, space, cdata, length, ctype, buf, w_object):
        W_CData.__init__(self, space, cdata, ctype)
        self.buf = buf
        self.length = length
        self.w_keepalive = w_object

    def get_array_length(self):
        return self.length

    def _sizeof(self):
        from pypy.module._cffi_backend import ctypearray
        ctype = self.ctype
        if isinstance(ctype, ctypearray.W_CTypeArray):
            return self.length * ctype.ctitem.size
        else:
            return W_CData._sizeof(self)

    def _repr_extra(self):
        from pypy.module._cffi_backend import ctypearray
        if self.w_keepalive is None:
            return "buffer RELEASED"
        obj_tp_name = self.space.type(self.w_keepalive).name
        if isinstance(self.ctype, ctypearray.W_CTypeArray):
            return "buffer len %d from '%s' object" % (self.length, obj_tp_name)
        else:
            return "buffer from '%s' object" % (obj_tp_name,)

    def enter_exit(self, exit_now):
        # for now, limited effect on PyPy
        if exit_now:
            self.w_keepalive = None


class W_CDataGCP(W_CData):
    """For ffi.gc()."""
    _attrs_ = ['w_original_cdata', 'w_destructor']
    _immutable_fields_ = ['w_original_cdata']

    def __init__(self, space, cdata, ctype, w_original_cdata, w_destructor):
        W_CData.__init__(self, space, cdata, ctype)
        self.w_original_cdata = w_original_cdata
        self.w_destructor = w_destructor
        self.register_finalizer(space)

    def _finalize_(self):
        self.invoke_finalizer()

    def invoke_finalizer(self):
        w_destructor = self.w_destructor
        if w_destructor is not None:
            self.w_destructor = None
            self.space.call_function(w_destructor, self.w_original_cdata)

    def detach_destructor(self):
        self.w_destructor = None
        self.may_unregister_rpython_finalizer(self.space)

    def enter_exit(self, exit_now):
        if exit_now:
            self.may_unregister_rpython_finalizer(self.space)
            self.invoke_finalizer()


W_CData.typedef = TypeDef(
    '_cffi_backend._CDataBase',
    __doc__ = "The internal base type for CData objects.  Use FFI.CData to "
              "access it.  Always check with isinstance(): subtypes are "
              "sometimes returned on CPython, for performance reasons.",
    __module__ = '_cffi_backend',   # attribute also visible on instances
    __name__ = '<cdata>',           # attribute also visible on instances
    __repr__ = interp2app(W_CData.repr),
    __nonzero__ = interp2app(W_CData.nonzero),
    __int__ = interp2app(W_CData.int),
    __long__ = interp2app(W_CData.long),
    __float__ = interp2app(W_CData.float),
    __complex__ = interp2app(W_CData.complex),
    __len__ = interp2app(W_CData.len),
    __lt__ = interp2app(W_CData.lt),
    __le__ = interp2app(W_CData.le),
    __eq__ = interp2app(W_CData.eq),
    __ne__ = interp2app(W_CData.ne),
    __gt__ = interp2app(W_CData.gt),
    __ge__ = interp2app(W_CData.ge),
    __hash__ = interp2app(W_CData.hash),
    __getitem__ = interp2app(W_CData.getitem),
    __setitem__ = interp2app(W_CData.setitem),
    __add__ = interp2app(W_CData.add),
    __radd__ = interp2app(W_CData.add),
    __sub__ = interp2app(W_CData.sub),
    __getattr__ = interp2app(W_CData.getattr),
    __setattr__ = interp2app(W_CData.setattr),
    __call__ = interp2app(W_CData.call),
    __iter__ = interp2app(W_CData.iter),
    __weakref__ = make_weakref_descr(W_CData),
    __dir__ = interp2app(W_CData.dir),
    __enter__ = interp2app(W_CData.descr_enter),
    __exit__ = interp2app(W_CData.descr_exit),
    )
W_CData.typedef.acceptable_as_base_class = False
