Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a public cython function that can receive c++ struct/instance or python object as parameter?

My Rectangle.h

namespace shapes {
    class Rectangle {
    public:
        int x0, y0, x1, y1;
        Rectangle();
        Rectangle(int x0, int y0, int x1, int y1);
        ~Rectangle();
        int getArea();
    };
}

My Rectangle.cpp

#include "Rectangle.h"
namespace shapes {
  Rectangle::Rectangle() { }
    Rectangle::Rectangle(int X0, int Y0, int X1, int Y1) {
        x0 = X0;
        y0 = Y0;
        x1 = X1;
        y1 = Y1;
    }
    Rectangle::~Rectangle() { }
    int Rectangle::getArea() {
        return (x1 - x0) * (y1 - y0);
    }
}

My rect.pyx

# distutils: language = c++
# distutils: sources = Rectangle.cpp

cdef extern from "Rectangle.h" namespace "shapes":
    cdef cppclass Rectangle:
        Rectangle() except +
        Rectangle(int, int, int, int) except +
        int x0, y0, x1, y1
        int getArea()

cdef class PyRectangle:
    cdef Rectangle c_rect
    def __cinit__(self, int x0, int y0, int x1, int y1):
        self.c_rect = Rectangle(x0, y0, x1, y1)
    def get_area(self):
        return self.c_rect.getArea()

cdef public int cythonfunc(PyRectangle py_rect):
    result = py_rect.get_area()
    return result

My main.cpp

#include <Python.h>

#include "rect.h"

#include "Rectangle.h"
#include <iostream>

int main (int argc, char *argv[])
{
  int result;
  Py_Initialize();

  PyInit_rect();
  shapes::Rectangle c_rect = shapes::Rectangle(0,0,2,1);
  result = cythonfunc(c_rect);
  std::cout<<result<<"\n";

  Py_Finalize();

  return 0;
}

My Makefile

all:
        cython3 --cplus rect.pyx
        c++ -g -O2 -c rect.cpp -o rect.o `python3-config --includes`
        c++ -g -O2 -c Rectangle.cpp -o Rectangle.o `python3-config --includes`
        c++ -g -O2 -c main.cpp -o main.o `python3-config --includes`
        c++ -g -O2 -o rect Rectangle.o rect.o main.o `python3-config --libs`

clean:
        rm -f rect rect.cpp rect.h *.o

My problem is related to the "cythonfunc" in rect.pyx. This is intended to be a public function that can be called from main with a rectangle struct/object as parameter, and return an area to main.cpp.

I've tried c struct and python object, both does not work for me. If I use this codes, the compiler gives an error of

Error compiling Cython file:
------------------------------------------------------------
...
    def __cinit__(self, int x0, int y0, int x1, int y1):
        self.c_rect = Rectangle(x0, y0, x1, y1)
    def get_area(self):
        return self.c_rect.getArea()

cdef public int cythonfunc(PyRectangle py_rect):
                          ^
------------------------------------------------------------

rect.pyx:19:27: Function declared public or api may not have private types

So I added "public" to PyRectangle, but got another error:

Error compiling Cython file:
------------------------------------------------------------
...
        Rectangle() except +
        Rectangle(int, int, int, int) except +
        int x0, y0, x1, y1
        int getArea()

cdef public class PyRectangle:
    ^
------------------------------------------------------------

rect.pyx:12:5: Type object name specification required for 'public' C class

If I change cythonfunc to:

cdef public int cythonfunc(Rectangle c_rect):
    result = c_rect.getArea()
    return result

I got error of:

In file included from main.cpp:3:0:
rect.h:21:42: warning: ‘cythonfunc’ initialized and declared ‘extern’
 __PYX_EXTERN_C DL_IMPORT(int) cythonfunc(shapes::Rectangle);
                                          ^
rect.h:21:42: error: ‘shapes’ has not been declared
main.cpp: In function ‘int main(int, char**)’:
main.cpp:17:29: error: ‘cythonfunc’ cannot be used as a function
   result = cythonfunc(c_rect);
                             ^

I can only success with passing separate x0, y0, x1, y1 as parameter to cythonfunc. Is there a correct way to passing a cpp struct/object or python object as parameter to a cython public function?

like image 782
Barpaum Avatar asked Nov 13 '16 08:11

Barpaum


1 Answers

With respect to your second attempt (which is probably the more sensible way of calling it from C++, although I'd pass by reference):

cdef public int cythonfunc(Rectangle c_rect):
    result = c_rect.getArea()
    return result

the issue is that it doesn't know what Rectangle is, since the Cython generated rect.h doesn't include Rectangle.h. The easiest way to fix this is to swap the order of the includes in main.cpp:

#include "Rectangle.h" // this one is now first
#include "rect.h"

The error "shapes has not been declared" was telling you this...


With respect you your first attempt, you were right that you have to make PyRectangle public. To do so you also need to specify a "type object name" as Cython tells you. You do so as shown here (although it honestly isn't hugely clear...):

cdef public class PyRectangle [object c_PyRect, type c_PyRect_t]:
    # ... as before

This ensures that PyRectangle is available to C/C++ as struct c_PyRect (and so the cythonfunc signature is int cythonfunc(struct c_PyRect *);.)

Also, the Python TypeObject defining PyRectangle is available as c_PyRect_t, but you don't need that.

like image 75
DavidW Avatar answered Oct 10 '22 04:10

DavidW