Cython extension class: how do I expose methods in an automatically generated C structure?

I have C ++ code that defines some classes that I need to use, but I need to be able to send these classes to Python code. In particular, I need to instantiate the class in C ++, create Python objects to serve as a wrapper for these C ++ objects, and then pass these Python objects to Python code for processing. This is just one part of a larger C ++ program, so it needs to be executed eventually in C ++ using the C / Python API.

To make my life easier, I used Cython to define extension classes (cdef classes) that serve as Python wrappers for my C ++ objects. I use a typical format in which the cdef class contains a pointer to the C ++ class, which is then initialized when the cdef class is instantiated. Since I also want to be able to replace the pointer, if I have an existing C ++ object for wrapping, I have added methods to my cdef classes in the accept() C ++ object and take its pointer. My other cdef classes successfully use the accept() method in Cython, for example, when one object belongs to another.

Here is an example of my Cython code:

MyCPlus.pxd

 cdef extern from "MyCPlus.h" namespace "mynamespace": cdef cppclass MyCPlus_Class: MyCPlus_Class() except + 

PyModule.pyx

 cimport MyCPlus from libcpp cimport bool cdef class Py_Class [object Py_Class, type PyType_Class]: cdef MyCPlus.MyCPlus_Class* thisptr cdef bool owned cdef void accept(self, MyCPlus.MyCPlus_Class &indata): if self.owned: del self.thisptr self.thisptr = &indata self.owned = False def __cinit__(self): self.thisptr = new MyCPlus.MyCPlus_Class() self.owned = True def __dealloc__(self): if self.owned: del self.thisptr 

The problem occurs when I try to access the accept() method from C ++. I tried using the public and api keywords in my cdef class and the accept() method, but I can't figure out how to expose this method in a C structure in automatically generating an .h file in Cython. No matter what I try, the C structure looks like this:

PyModule.h (Cython Auto Generation)

 struct Py_Class { PyObject_HEAD struct __pyx_vtabstruct_11PyModule_Py_Class *__pyx_vtab; mynamespace::MyCPlus_Class *thisptr; bool owned; }; 

I also tried typing self as Py_Class , and I even tried forward-decloring Py_Class with the keywords public and api . I also experimented with creating a static accept() method. Nothing I tried works to expose the accept() method so that I can use it from C ++. I tried to access it via __pyx_vtab , but I got a compiler error, "invalid use of incomplete type". I searched quite a bit, but did not see a solution for this. Can someone help me? Please thanks!

+3
source share
3 answers

As you pointed out in comment , it seems that the __pyx_vtab member __pyx_vtab used only for Cython, since it does not even define the structure, enter it in the exported header.

Adding to your answer, one approach could also be:

 cdef api class Py_Class [object Py_Class, type Py_ClassType]: ... cdef void accept(self, MyCPlus.MyCPlus_Class &indata): ... # do stuff here ... cdef api void (*Py_Class_accept)(Py_Class self, MyCPlus.MyCPlus_Class &indata) Py_Class_accept = &Py_Class.accept 

Basically, we define a pointer to a function and set it to the extension method that we want to open. This is not so different from your cdef 'd response function; the main difference would be that we can define our methods, as usual, in defining a class without having to duplicate the functionality or calls of methods / functions of another function to expose it. One caveat is that we would have to define our function pointer signature almost verbatim for a method that is in addition to the extension type self (in this case), etc .; then again this applies to regular functions.

Notice that I tried this on a C-level Cython .pyx file, I do not have and do not intend to test it in the CPP implementation file. But hopefully this can work just as well, I think.

+2
source

This is actually not a solution, but I came up with a workaround for my problem. I still hope for a solution that allows me to tell Cython about opening the accept() method for C ++.

My workaround is that I wrote a separate function for my Python class (and not the method). Then I gave the api keyword for both my Python class and the new function:

 cdef api class Py_Class [object Py_Class, type PyType_Class]: (etc.) cdef api Py_Class wrap_MyCPlusClass(MyCPlus.MyCPlus_Class &indata): wrapper = Py_Class() del wrapper.thisptr wrapper.thisptr = &indata wrapper.owned = False return wrapper 

This is a bit inconvenient with the number of different classes that I need to wrap, but at least Cython puts the function in the API, where it is easy to use:

 struct Py_Class* wrap_MyCPlusClass(mynamespace::MyCPlusClass &); 
+1
source

You probably want to use cpdef instead of cdef when declaring accept . See documents :

Called from Python and C
* Declared using the cpdef statement.
* Can be summoned from anywhere, because it uses little Keaton magic.
* Uses faster C calling conventions when called from other Cython code.

Try it!

0
source

Source: https://habr.com/ru/post/1259664/


All Articles