How to write a complete Python wrapper around a C Struct using Cython?
Asked Answered
D

1

9

I am writing a a high level interface for a C library for Python using Cython.
I have an extension Type A that initializes the library with a pointer to a more complex C context structure c_context. The pointer is saved in A.
A also has a def function which in turn creates another extension Type B initializing another C structure with a library function call. This structure is needed for the subsequent library calls made in B.
B needs the c_context pointer from A which is wrapped by me within the extension type py_context in order to pass it to __cinit__ from B:

#lib.pxd (C library definitions)
cdef extern from "lib.h":
    ctypedef struct c_context:
        pass

#file py_context.pxd
from lib cimport c_context

cdef class py_context:
    cdef c_context *context
    cdef create(cls, c_context *context)
    cdef c_context* get(self)

#file py_context.pyx
def class py_context:
    @staticmethod
    cdef create(cls, c_context *c):   
        cls = py_nfc_context()  
        cls.context = c  
        return cls

    cdef c_context* get(self):
        return self.context

Passing the wrapper with the correct C context works perfectly.

Now I need to get the C struct out of py_context again and save it in B. I added cdef c_context get(self) to py_context.pxd/pyx. Calling py_context.get() from Bs __cinit__ results in: AttributeError: py_context object has no attribute get.

It seems like I do not get my head around when to call cdef functions in Cython.

So my question is: What is the best way to extract the C struct from my wrapper class again?

Disentitle answered 23/6, 2016 at 7:6 Comment(1)
Have you read this? docs.cython.org/src/tutorial/clibraries.htmlOribelle
A
6

The trouble is that Cython doesn't know the data type of your py_context variable at compile time. Calls to cdef functions are resolved at compile time and there exists no mechanism to figure it out at runtime by attribute lookup (as with normal Python functions).

[Note that def functions written within Cython are still compiled and can specify data types, so are perfectly capable of calling cdef functions if they have the right information.]

You don't give the relevant code where it's going wrong (the constructor of type B), but here's an very simplified example which will hopefully give you a couple of ways to fix it:

cdef class A:
    cdef f(self):
        return

def f1(var):
    var.f()

#f1(A()) # will fail at runtime with an attribute error

In f1 the type of var is unknown, and thus you can't call cdef functions.

def f2(A var):
    var.f()

f2(A()) # will work
f2(1) # will fail, int can't be converted to A

In f2 the type of var is constrained to be A, and therefore it can happily call cdef functions associated with A. If you pass something that isn't A to it you'll get a TypeError at runtime.

def f3(var):
    cdef A another_reference_to_var = var # this does test that the types match
    another_reference_to_var.f()

f3(A()) # will work
f3(1) # will fail, int can't be converted to A

The function f3 can take a variable of any type. However, when you assign it to another_reference_to_var which is cdefed to be an A it checks that the type matches (and raises a runtime exception if it doesn't). Since another_reference_to_var is known to be A at compile time, you can call As cdef functions.

Essentially, you need to specify the type of the relevant input to your __cinit__ function.

Anoint answered 23/6, 2016 at 18:12 Comment(2)
+1 This is a better answer so I promptly removed mine since it was a bit misleading. Here is a url with a full example of wrapping a struct for OP to see if he needs.Flocculus
Works like a charm. The missing type information in Bs __cinit__ was the problem. Adding py_context resolves the problem. Your information should be added to the Cython docs since they are a bit vague at times.Disentitle

© 2022 - 2024 — McMap. All rights reserved.