truf
truf

Reputation: 3111

Cython: declare a PyCapsule_Destructor in pyx file

I don't know python and trying to wrap an existing C library that provides 200 init functions for some objects and 200 destructors with help of PyCapsule. So my idea is to return a PyCapsule from init functions` wrappers and forget about destructors that shall be called automatically.

According to documentation PyCapsule_New() accepts:
typedef void (*PyCapsule_Destructor)(PyObject *);
while C-library has destructors in a form of:
int foo(void*);

I'm trying to generate a C function in .pyx file with help of cdef that would generate a C-function that will wrap library destructor, hide its return type and pass a pointer taken with PyCapsule_GetPointer to destructor. (pyx file is programmatically generated for 200 functions).

After a few experiments I end up with following .pyx file:

from cpython.ref cimport PyObject
from cpython.pycapsule cimport PyCapsule_New, PyCapsule_IsValid, PyCapsule_GetPointer

cdef void stateFree( PyObject *capsule ):
     cdef:
        void * _state
     # some code with PyCapsule_GetPointer

def stateInit():
    cdef:
        void * _state
    return PyCapsule_New(_state, "T", stateFree)

And when I'm trying to compile it with cython I'm getting: Cannot assign type 'void (PyObject *)' to 'PyCapsule_Destructor'
using PyCapsule_New(_state, "T", &stateFree) doesn't help.

Any idea what is wrong?

UPD:

Ok, I think I found a solution. At least it compiles. Will see if it works. I'll bold the places I think I made a mistake:

from cpython.ref cimport PyObject
from cpython.pycapsule cimport PyCapsule_New, PyCapsule_IsValid, PyCapsule_GetPointer, PyCapsule_Destructor

cpdef void stateFree( object capsule ):
cdef:
void* _state
_state = PyCapsule_GetPointer(capsule, "T")
print('destroyed')

def stateInit():
cdef:
int _state = 1
print ("initialized")
return PyCapsule_New(_state, "T", < PyCapsule_Destructor >stateFree)

Upvotes: 1

Views: 398

Answers (1)

DavidW
DavidW

Reputation: 30889

The issue is that Cython distinguishes between

  • object - a Python object that it knows about and handles the reference-counting for, and
  • PyObject*, which as far as it's concerned is a mystery type that it basically nothing about except that it's a pointer to a struct.

This is despite the fact that the C code generated for Cython's object ends up written in terms of PyObject*.

The signature used by the Cython cimport is ctypedef void (*PyCapsule_Destructor)(object o) (which isn't quite the same as the C definition. Therefore, define the destructor as

cdef void stateFree( object capsule ):

Practically in this case the distinction makes no difference. It matters more in cases where a function steals a reference or returns a borrowed reference. Here capsule has the same reference count on both the input and output of the function whether Cython manages it or not.

In terms of your edited-in solution:

  • cpdef is wrong for stateFree. Use cdef since it is not a function that should be exposed in a Python interface (and if you use cpdef it isn't obvious whether the Python or C version is passed as a function pointer).
  • You shouldn't need the cast to PyCapsule_Destructor and should avoid it because casts can easily hide bugs.

Can I just take a moment to express my general dislike for PyCapsule (it's occasionally useful for passing an opaque type through Python code without touching it, but for anything more I think it's usually better to wrap it properly in a cdef class). It's possible you've thought about it and it is the right tool for the job, but I'm putting this warning in to try to discourage people in the future who might be trying to use it on a more "copy-and-paste" basis.

Upvotes: 3

Related Questions