5 votes

Comment créer une fonction publique cython qui peut recevoir en paramètre une structure/instance c++ ou un objet python ?

Mon 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();
    };
}

Mon 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);
    }
}

Mon 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

Mon 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;
}

Mon 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

Mon problème est lié au "cythonfunc" dans rect.pyx. Il s'agit d'une fonction publique qui peut être appelée depuis main avec une structure/objet rectangle comme paramètre, et qui renvoie une zone à main.cpp.

J'ai essayé le struct c et l'objet python, les deux ne fonctionnent pas pour moi. Si j'utilise ces codes, le compilateur donne une erreur de

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

J'ai donc ajouté "public" à PyRectangle, mais j'ai obtenu une autre erreur :

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

Si je change cythonfunc en :

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

J'ai eu une erreur de :

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);
                             ^

Je ne peux réussir qu'en passant séparément x0, y0, x1, y1 comme paramètre à cythonfunc. Existe-t-il une manière correcte de passer une structure/objet cpp ou un objet python comme paramètre à une fonction publique cython ?

2voto

DavidW Points 13614

En ce qui concerne votre deuxième tentative (qui est probablement la manière la plus sensée de l'appeler à partir de C++, bien que je passerais par référence) :

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

le problème est qu'il ne sait pas ce que Rectangle c'est que, depuis que la version de Cython générée rect.h n'inclut pas Rectangle.h . La manière la plus simple de résoudre ce problème est d'intervertir l'ordre des inclusions dans le fichier main.cpp :

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

L'erreur "les formes n'ont pas été déclarées" vous le disait...


En ce qui concerne votre première tentative, vous aviez raison de dire qu'il faut faire PyRectangle public. Pour ce faire, vous devez également spécifier un "nom d'objet de type", comme vous l'indique Cython. Vous le faites comme indiqué aquí (même si, honnêtement, ce n'est pas très clair...) :

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

Cela garantit que PyRectangle est disponible pour C/C++ en tant que struct c_PyRect (et donc le cythonfunc la signature est int cythonfunc(struct c_PyRect *); .)

De même, le programme Python TypeObject définir PyRectangle est disponible en tant que c_PyRect_t mais vous n'en avez pas besoin.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X