3

The Problem:

Error compiling Cython file:
------------------------------------------------------------
...
cpdef Py_GetRemoteDevice(PyChannel Chan):
    ret_val = mod_one.PyRemoteDevice()
    cdef core.RemoteDevice  * tmp
    with nogil:
        tmp = core.GetRemoteDevice(Chan.__instance)
    ret_val.__set_ptr(tmp)
                        ^
------------------------------------------------------------

core/mod_two.pyx:11589:25: Cannot convert 'RemoteDevice *' to Python object

The Background: I have a fairly large C library I'm wrapping with Cython. Initially I had everything in two files, core.pyx and core.pxd. However, the more of the library that was wrapped the larger the core.c file generated. 2.5 million lines and counting. The compile time and memory usage became cumbersome.

I decided to split things into multiple .pyx files. That's when things that used to work stopped working.

Disclaimer: Please keep in mind that I've greatly simplified things for this question. The wrapped library is somewhat large and proprietary.

The Details:
core.pxd - contains all the necessary components from the C library
I'm wrapping. Also a simple Void * wrapper class declaration.

cdef class Void:
    cdef void *__void
    cdef __set_ptr(self, void *ptr)

cdef extern from "core.h":
    ctypedef unsigned char U8
    ctypedef unsigned int U32

    void OS_MemSet(U8 *dest, U8 byte, U32 len) nogil

cdef extern from "mod_one.h":
    cdef struct _RemoteDevice
    ctypedef _RemoteDevice RemoteDevice

cdef extern from "mod_two.h":
    cdef struct _Channel
    ctypedef _Channel Channel

core.pyx - not a whole lot is needed here, really it's just the implementation of the Void * wrapper.

cdef class Void:
    cdef __set_ptr(self, void *ptr):
        self.__void = ptr

mod_one.pxd - declares the wrapper class for the RemoteDevice C struct

cimport core

cdef class PyRemoteDevice:
    cdef core.RemoteDevice *__instance
    cdef __set_ptr(self, core.RemoteDevice *ptr)

mod_one.pyx - defines the RemoteDevice wrapper class. Note the __set_ptr function, which is cdef'd and takes a RemoteDevice *

from cpython.mem cimport PyMem_Malloc, PyMem_Free
import core
cimport core

cdef class PyRemoteDevice:
    def __cinit__(self):
        self.__instance = <core.RemoteDevice *>PyMem_Malloc(sizeof(core.RemoteDevice))

    def __init__(self):
        core.OS_MemSet(<core.U8 *>self.__instance, <core.U8>0, sizeof(core.RemoteDevice))

    cdef __set_ptr(self, core.RemoteDevice *ptr):
        self.__instance = ptr

    def __dealloc__(self):
        if self.__instance is not NULL:
            PyMem_Free(self.__instance)
            self.__instance = NULL

mod_two.pxd - declares the Channel wrapper class, which I'm only showing here because it's used in the function that is causing me problems.

cimport core
cimport mod_one

cdef class PyChannel:
    cdef core.Channel *__instance
    cdef __set_ptr(self, core.Channel *ptr)

mod_two.pyx - defines the Channel wrapper class, and declares the Py_GetRemoteDevice function, which wraps the GetRemoteDevice function from the C library.

from cpython.mem cimport PyMem_Malloc, PyMem_Free import core cimport core import mod_one cimport mod_one

cdef class PyChannel:
    def __cinit__(self):
        self.__instance = <core.Channel *>PyMem_Malloc(sizeof(core.Channel))

    def __init__(self):
        core.OS_MemSet(<core.U8 *>self.__instance, <core.U8>0, sizeof(core.Channel))

    cdef __set_ptr(self, core.Channel *ptr):
        self.__instance = ptr

    def __dealloc__(self):
        if self.__instance is not NULL:
            PyMem_Free(self.__instance)
            self.__instance = NULL

cpdef Py_GetRemoteDevice(PyChannel Chan):
    ret_val = mod_one.PyRemoteDevice()
    cdef core.RemoteDevice  * tmp
    with nogil:
        tmp = core.GetRemoteDevice(Chan.__instance)
    ret_val.__set_ptr(tmp)
    return ret_val

The problem I'm getting is that when Cythonizing mod_two, I get the following error:

Error compiling Cython file:
------------------------------------------------------------
...
cpdef Py_GetRemoteDevice(PyChannel Chan):
    ret_val = mod_one.PyRemoteDevice()
    cdef core.RemoteDevice  * tmp
    with nogil:
        tmp = core.GetRemoteDevice(Chan.__instance)
    ret_val.__set_ptr(tmp)
                        ^
------------------------------------------------------------

core/mod_two.pyx:11589:25: Cannot convert 'RemoteDevice *' to Python object

I'm a bit confused, because this used to work when I had everything in one module. One difference I can see is that all my wrapper classes were wholly defined in core.pyx because I wasn't needing to share them between modules. Now I need to share them, so I've split them into .pyx and .pxd files.

Can anyone point me in the right direction? I've scoured the Cython docs and Google but so far I haven't found anything that answers my question.

Thanks, and please let me know if any additional information is needed!

rahvin74
  • 35
  • 7
  • looks like you need a type annotation on `ret_val`? – chrisb Aug 30 '17 at 23:51
  • @chrisb, I wondered about that but I haven't been able to get it to work. Probably because I'm not sure what it should look like. I've tried mod_one.PyRemoteDevice ret_val = mod_one.PyRemoteDevice() with no luck. I'm going to try it again though, just to make sure. – rahvin74 Aug 31 '17 at 00:38
  • Ok, @chrisb, you are correct. As soon as I typed my last comment, I realized my mistake. I needed `cdef` in front of what I posted last, so it would be `cdef mod_one.PyRemoteDevice ret_val = mod_one.PyRemoteDevice()`. I feel a bit silly! If you make your comment into an answer, I'll accept it. – rahvin74 Aug 31 '17 at 00:51

1 Answers1

1

Accessing c-level functions on extension types requires cython to know the exact type, otherwise it is treated as a generic Python object. So a type annotation like this is needed

cdef mod_one.PyRemoteDevice ret_val = ...
chrisb
  • 49,833
  • 8
  • 70
  • 70