Convert C++ class pointer to Python object

1,153 views
Skip to first unread message

Rafał Lalik

unread,
Oct 25, 2017, 10:45:36 AM10/25/17
to cython-users
I have a class (A), which returns pointer to another class (B). I would like to port it into cython and be able to return python object B from python object A, but have a problem with defining properly function which return python object. Here is my code (it's simplified version of the problem) (all the code is also attached).

// a.h
#ifndef CLASS_A
#define CLASS_A
#include "b.h"

class A {
public:
  A
() {}
  B
* getB();
};
#endif

// a.cpp
#include "a.h"

B
* A::getB() { return new B; }

// b.h
#ifndef CLASS_B
#define CLASS_B

class B {
public:
  B
() {}
 
void nop() const;
};
#endif


// b.cpp
#include "b.h"
#include <cstdio>

void B::nop() const
{
  std
::printf("My address is %p\n", this);
}

and following cython files

// class_a.pxd
from class_b cimport _B

cdef
extern from "a.h":
  cdef cppclass _A
"A":
    _A
() except +
    _B
* getB()

// class_a.pyx
from class_b cimport _B

cdef
class A:
  cdef _A c_obj
   
def __cinit__(self):
     
self.c_obj = _A()

 
def getB(self):
    _b
= self.c_obj.getB()
    b
= B(*_b)
   
return b


// class_b.pxd
cdef extern from "b.h":
  cdef cppclass _B
"B":
    _B
() except +

 
void nop()

// class_b.pyx
cdef class B:
  cdef _B c_obj
   
def __cinit__(self, _B _Object=None):
     
if _B is None:
       
self.c_obj = _B()
     
else:
       
self.c_obj = _Object

  def nop(self):
   
self.c_obj.nop()

// setup.py

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

modules
= [
 
Extension(
   
"class_a",
    sources
= [ "class_a.pyx" ],
    language
= "c++",
    library_dirs
= [ "." ],
    libraries
= [ "class" ],
 
),
 
Extension(
   
"class_b",
    sources
= [ "class_b.pyx" ],
    language
= "c++",
    library_dirs
= [ "." ],
    libraries
= [ "class" ],
 
),
]

setup
(
  name
="pyClass",
  package_dir
={'' : ''},
  ext_modules
=cythonize(modules, build_dir="./")
)

And here is the build order
// build.sh
g++ -fPIC -o class_a.o a.cpp -c
g
++ -fPIC -o class_b.o b.cpp -c
g
++ -shared -Wl,-soname,libclass.so -o libclass.so class_a.o class_b.o

python setup
.py build_ext install

The error I have is:

Compiling class_a.pyx because it changed.
Compiling class_b.pyx because it changed.
[1/2] Cythonizing class_a.pyx

Error compiling Cython file:
------------------------------------------------------------
...
   def __cinit__(self):
       self.c_obj = _A()

   def getB(self):
       _b = self.c_obj.getB()
       b = B(*_b)
          ^
------------------------------------------------------------

class_a.pyx:10:12: undeclared name not builtin: B

Error compiling Cython file:
------------------------------------------------------------
...
   def __cinit__(self):
       self.c_obj = _A()

   def getB(self):
       _b = self.c_obj.getB()
       b = B(*_b)
             ^
------------------------------------------------------------

class_a.pyx:10:15: Cannot convert '_B *' to Python object
cython_classes.tar.gz

Robert Bradshaw

unread,
Oct 25, 2017, 6:49:15 PM10/25/17
to cython...@googlegroups.com
On Wed, Oct 25, 2017 at 4:52 AM, Rafał Lalik <rafal...@gmail.com> wrote:
> The error I have is:
>
>> Compiling class_a.pyx because it changed.
>> Compiling class_b.pyx because it changed.
>> [1/2] Cythonizing class_a.pyx
>>
>> Error compiling Cython file:
>> ------------------------------------------------------------
>> ...
>> def __cinit__(self):
>> self.c_obj = _A()
>>
>> def getB(self):
>> _b = self.c_obj.getB()
>> b = B(*_b)
>> ^
>> ------------------------------------------------------------
>>
>> class_a.pyx:10:12: undeclared name not builtin: B

This is because you import the class as _B and are trying to invoke it as B.

>> Error compiling Cython file:
>> ------------------------------------------------------------
>> ...
>> def __cinit__(self):
>> self.c_obj = _A()
>>
>> def getB(self):
>> _b = self.c_obj.getB()
>> b = B(*_b)
>> ^
>> ------------------------------------------------------------
>>
>> class_a.pyx:10:15: Cannot convert '_B *' to Python object

__init__ (and, consequently) __cinit__ methods cannot take non-Python
arguments. The typical solution to this is to add a static create
method that can take such arguments. E.g.

cdef class B(object):
cdef B* c_obj
@staticmethod
cdef B create(B* ptr):
cdef B result = B()
result.c_obj = ptr
return result

Rafał Lalik

unread,
Oct 27, 2017, 5:31:22 AM10/27/17
to cython-users
>>    def getB(self):
>>        _b = self.c_obj.getB()
>>        b = B(*_b)
>>           ^
>> ------------------------------------------------------------
>>
>> class_a.pyx:10:12: undeclared name not builtin: B

This is because you import the class as _B and are trying to invoke it as B.

Yes, correct. But I didn't know how to handle this with pxd files. But later I tried some things and made it working. Would be great if cython dicumentation contain better example on C++ class and pxd files. 
 
__init__ (and, consequently) __cinit__ methods cannot take non-Python
arguments. The typical solution to this is to add a static create
method that can take such arguments. E.g.

cdef class B(object):
    cdef B* c_obj
    @staticmethod
    cdef B create(B* ptr):
        cdef B result = B()
        result.c_obj = ptr
        return result

Thanks for this code, I put it into my code and now it works.

I had to make also a several more changes, so I will not post all the files again, I just attach the whole code as an archive. Maybe someone who has the same problem will find it useful. 
cython_classes_v2.zip
Reply all
Reply to author
Forward
0 new messages