Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return a struct from C to Python using Cython

I am trying to pass a struct back into my Python from a c file. Let's say I have a file pointc.c like this:

typedef struct Point {
    int x;
    int y;
} Point;

struct Point make_and_send_point(int x, int y);

struct Point make_and_send_point(int x, int y) {
    struct Point p = {x, y};
    return p;
}

Then I set-up a point.pyx file like this:

"# distutils: language = c"
# distutils: sources = pointc.c

cdef struct Point:
    int x
    int y

cdef extern from "pointc.c":
    Point make_and_send_point(int x, int y)

def make_point(int x, int y):
    return make_and_send_point(x, y) // This won't work, but compiles without the 'return' in-front of the function call

How do I get the returned struct into my Python? Is this kind of thing only possible by creating a struct in the Cython and sending by reference to a void c function?

As a reference, my setup.py is:

from distutils.core import setup, Extension
from Cython.Build import cythonize

setup(ext_modules = cythonize(
      "point.pyx",
      language="c"
     )
)
like image 802
Redherring Avatar asked Mar 02 '18 15:03

Redherring


2 Answers

Most typically you would write some kind of wrapper class that holds the c-level struct, for example:

# point.pyx
cdef extern from "pointc.c":
    ctypedef struct Point:
        int x
        int y
    Point make_and_send_point(int x, int y)

cdef class PyPoint:
    cdef Point p

    def __init__(self, x, y):
        self.p = make_and_send_point(x, y)

    @property
    def x(self):
       return self.p.x

    @property
    def y(self):
        return self.p.y

In usage

>>> import point
>>> p = point.PyPoint(10, 10)
>>> p.x
10
like image 67
chrisb Avatar answered Oct 21 '22 11:10

chrisb


Cython's default behaviour given a struct is to convert it to a Python dictionary, which may be good enough for you. (This only works for structs made up of simple types though).

There's a couple of reasons why this isn't working. First you should do cdef extern from from headers, not source files, otherwise you get errors about multiple definitions (I assume this is just a mistake in creating your minimal example). Second you need to put the definition of Point within your cdef extern block:

cdef extern from "pointc.h":
    cdef struct Point:
        int x
        int y

If you don't do that then Cython creates a mangled internal name for your struct (__pyx_t_5point_Point) which doesn't match the C function signature and thus it fails.

With this corrected, you get the correct default behaviour of converting structs to dicts. (This should work both ways - you can convert dicts back to structs). In the event that this isn't what you want, follow @chrisb's answer

like image 5
DavidW Avatar answered Oct 21 '22 10:10

DavidW