0

How can I convert a Python object argument in a Cython method defined using def to a C++ type? I am attempting to provide a Cython wrapper class for a C++ library, as described in the Using C++ in Cython section of the Cython documentation.

Here is an example that demonstrates my issue.

File foo.h:

namespace ns {
  class Foo:
    public:
      Foo();
      dosomething(std::shared_ptr<Bar>);
}

File bar.h:

namespace ns {
  class Bar:
    public:
      Bar();
}

File foo.pyx:

from libcpp.memory cimport shared_ptr

cdef extern from 'bar.h' namespace 'ns':

    cdef cppclass Bar:
        Bar() except +


cdef extern from 'foo.h' namespace 'ns':

    cdef cppclass Foo:
        Foo() except +
        void dosomething(shared_ptr[Bar])


cdef class PyBar:

    cdef Bar* thisptr

    def __cinit__(self):
        self.thisptr = new Bar()

    def __dealloc__(self):
        del self.thisptr


cdef class PyFoo:

    cdef Foo* thisptr

    def __cinit__(self):
        self.thisptr = new Foo()

    def __dealloc__(self):
        del self.thisptr

    def dosomething(self, bar):
        self.thisptr.dosomething(bar)

File setup.py:

from setuptools import Extension
from setuptools import setup

from Cython.Build import cythonize

extensions = [
    Extension(
        'foo',
        sources=[
            'foo.pyx',
            'foo.cpp',
            'bar.cpp',
        ],
        language='c++',
        include_dirs=['.'],
        extra_compile_args=['-std=c++11'],
    ),
]

setup(
    ext_modules=cythonize(extensions),
)

The error that occurs when I try to compile this:

$ python setup.py build_ext
Compiling foo.pyx because it changed.
[1/1] Cythonizing foo.pyx

Error compiling Cython file:
------------------------------------------------------------
...

def __dealloc__(self):
    del self.thisptr

def dosomething(self, bar):
    self.thisptr.dosomething(bar)
                ^
------------------------------------------------------------

foo.pyx:38:33: Cannot convert Python object to 'shared_ptr[Bar]'
argentpepper
  • 4,202
  • 3
  • 33
  • 45
  • Possible duplicate of [Convert Python program to C/C++ code?](https://stackoverflow.com/q/4650243/608639) – jww Nov 21 '19 at 13:32

1 Answers1

0

I am afraid I have to say, the foo and bar is really a foolish example, have no idea what the C++ codes want to complete, no logic and no implementation. But I also made up such a foolish example based on yours, and made it work. It's tricky and buggy, hope that will help you a little.

I use jupyter notebook to simplify the compilation:

%%file foo.h
#include <memory>

namespace ns {

  class Bar{
    public:
      Bar(){};
  };

  class Foo{
    public:
      Foo(): bar_addr(0) {}
      void dosomething(std::shared_ptr<Bar> sp){bar_addr = (size_t)sp.get();}
      size_t get_bar_addr(){return bar_addr;}
    private:
      size_t bar_addr;
  };
}

Cython's parts:

%%cython -f -+ -a
# distutils: include_dirs = .

from libcpp.memory cimport shared_ptr


cdef extern from 'foo.h' namespace 'ns':

    cdef cppclass Foo:
        Foo() except +
        void dosomething(shared_ptr[Bar])
        size_t get_bar_addr()

    cdef cppclass Bar:
        Bar() except +


cdef class PyBar:

    cdef Bar* thisptr

    def __cinit__(self):
        self.thisptr = new Bar()

    def __dealloc__(self):
        del self.thisptr

    def addr(self):
        return <size_t><void*>self.thisptr


cdef class PyFoo:

    cdef Foo* thisptr
    # There is a bug here, see the comments
    cdef PyBar owned_bar  # keep this bar to prevent the underlying pointer losing. 
    cdef shared_ptr[Bar] owned_bar_sp # keep this bar shared_ptr to prevent the underlying pointer from deleting.

    def __cinit__(self):
        self.thisptr = new Foo()

    def __dealloc__(self):
        del self.thisptr

    def dosomething(self, PyBar bar):
        self.owned_bar = bar  
        print("Address passed in: ", self.owned_bar.addr())
        self.owned_bar_sp = shared_ptr[Bar](<Bar*><size_t>self.owned_bar.addr())
        self.thisptr.dosomething(self.owned_bar_sp) 
        print("Address got      : ", self.thisptr.get_bar_addr())

Tests:

bar = PyBar()
foo = PyFoo()

foo.dosomething(bar)

About your question "How to convert Python object to C++ type in Cython ", I think the answer is "Can" or "Cannot" or "Possible", depending on the contexts. In this particular example, "Cannot". You have to do some constructions or conversions in Cython, which can act as a solid bridge between Python and C/C++

oz1
  • 938
  • 7
  • 18
  • 1
    I don't think it should be made this way: 1) the whole owned_bar business is unnecessary, bar will not be deleted while it is used in the dosomething. 2) There is a doubled free of PyBar.thisptr after calling dosomething: once by __dealoc__ once through shared_ptr. – ead Nov 01 '17 at 05:55
  • 1
    Shared_ptr in the cpp-interface makes some problems... maybe Bar.thisptr should be of type shared_ptr[Bar] – ead Nov 01 '17 at 06:03
  • @ead Good point! I instinctively feel it's buggy. Feel free to edit it. – oz1 Nov 01 '17 at 06:18