Search code examples
pythoninheritancecythonbullet

Instantiating a C++ struct derived from a class in cython


I am trying to interface Python to the Bullet Physics C++ library using Cython. I would like to do this at C speeds, or near C speeds with minimal variable packing and unpacking from Python. I was able to use this approach to create a Cython interface to OpenGL (though OpenGL has a much simpler, non object-oriented C API, compared to Bullet Physics). My OpenGL interfaces works well but progress has been slow on interfacing with Bullet Physics.

I have read the Cython C++ tutorial and other related posts here but so far no luck.

The organization of Bullet Physics is a bit complex, but for this example hopefully it will be enough to know that btDbvtBroadphase is a struct which is derived from the btBroadphaseInterface class. As you probably know, structs can be derived from classes (inherit from them) and there isn't too much difference between the two--in C++ at least. However, Cython seems to only allow "new" to be done on classes and not on structs. I think that leads to my first problem.

The declarations in the Bullet Physics .h files look like this:

class btBroadphaseInterface
{
    ...

and

struct  btDbvtBroadphase : btBroadphaseInterface
{
    ...

The very simple line of C++ code I'm trying to replicate efficiently in Cython is this:

btBroadphaseInterface* broadphase = new btDbvtBroadphase();

So, since in Cython I can't do a "new" on a btDbvtBroadphase struct as is done in C++, how do I instantiate this struct, derived from the class, so that all initializers are called and methods set up inside?

I have seen some references to limitations on inheritance within Cython.

I can obviously malloc() the space for the structure, but that doesn't help me much, I think. I can't see how any sort of casting would help.

I have the Cython ".pxd" stuff showing how I do the cdef extern from the .h file but I didn't think that would help clarify this question, so I'll omit that unless someone thinks it might be helpful.

I have tried many variants of cdefs with no luck. Here is what it looks like now:

cdef extern from "bullet/btBulletDynamicsCommon.h":
    cdef cppclass btBroadphaseInterface:
        pass

    cdef cppclass btDbvtBroadphase(btBroadphaseInterface):
        pass

When I try to write a constructor that will do a "new" on this struct derived from a class, like this:

cdef class PbtDbvtBroadphase:
    cdef btBroadphaseInterface *bp

    def __cinit__(self):
        self.bp=<btBroadphaseInterface *> new btDbvtBroadphase()

The compiler fails with:

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

cdef class PbtDbvtBroadphase: cdef btBroadphaseInterface *bp

def __cinit__(self):
    self.bp=<btBroadphaseInterface *> new btDbvtBroadphase()

^

fun4.pyx:173:46: new operator can only be applied to a C++ class

Edit: improved title, which I had initially entered as just a string of keywords

Edit2: added the cdefs and compiler output

I'm accepting the very patient @DavidW's answer since it demonstrated that things should work. I think what was happening was that I had a stray .pxd file named with the same basename as my .pyx file, and that was being integrated into the build even though I was not using distutils but rather just cython from the command line. The .pxd was left over from an earlier test when I thought my build method might not be giving the same results as the distutils/setup.py approach. I became convinced my build method wasn't related to the problem and went back to my preferred approach (putting cdefs in the .pyx file and just compiling and linking via a simple shell script as shown in one of my comments below) but the .pxd file was still around and silently still getting pulled in by the cython compiler without my knowledge. I discovered this when I noticed that I was getting "redeclared" errors on compilation. I missed the "redeclared" which were scrolling away and only saw the errors on the new constructor attempts.


Solution

  • The easiest option is just to tell Cython it's a class:

    cdef cppclass btDvbtBroadphase(btBroadphaseInterface):
        #etc
    

    The difference between struct and class in C++ is only really the default visibility of members. From Cython's point of view it behaves the same and so it doesn't matter that what you tell Cython doesn't match the C++ code.

    (It may also be worth omitting telling Cython about the inheritance. If you don't use the inheritance in Cython then it doesn't need to know. It's often easier not to reproduce the full hierarchy but only the bits you need)


    Edit: the following code works for me:

    header.hpp:

    class btBroadphaseInterface {
    
    };
    
    struct btDbvtBroadphase : btBroadphaseInterface {
    
    };
    

    pymod.pyx:

    cdef extern from "header.hpp":
        cppclass btBroadphaseInterface:
            pass
    
        cppclass btDbvtBroadphase(btBroadphaseInterface):
            pass
    
    cdef class PyBroadphase:
        cdef btBroadphaseInterface* pt
        def __cinit__(self):
            self.pt = new btDbvtBroadphase()
    
        def __dealloc__(self):
            del self.pt
    

    setup.py

    from distutils.core import setup, Extension
    from Cython.Build import cythonize
    
    setup(ext_modules = cythonize(Extension(
               "pymod",                                # the extension name
               sources=["pymod.pyx",], # the Cython source and
                                                      # additional C++ source files
               language="c++",                        # generate and compile C++ code
          )))