SGirardin
SGirardin

Reputation: 31

cython - wrap a cpp class aggregating an other

I would like to create a wrapper for a cpp class with Cython. But this class aggregates an other one and I don't know if i need to also create a wrapper for this class as I don't want to call this second class from Python.

There are the cpp classes:

testclass.hpp

#ifndef TESTCLASS_H
#define TESTCLASS_H
#include "ocean.hpp"

class TestClass {
    private:
        Ocean _ocean;

    public:
        int x, y;
        TestClass();
        ~TestClass();
        int Multiply(int a, int b);
};
#endif

testclass.cpp

#include "testclass.hpp"
TestClass::TestClass()
{
    x = 5;
    y = 1;
    _ocean = Ocean();
}

TestClass::~TestClass()
{
    std::cout << "Calling destructor" << std::endl;
}

int TestClass::Multiply(int a, int b)
{
    return a*b;
}

ocean.hpp

#ifndef OCEAN_H
#define OCEAN_H
class Ocean {
    public:
        double _depth;
        double _rho;

        Ocean();
        virtual ~Ocean();
        void setwaterdepth(double d);
};
#endif

I would like to wrap the test class only, This is what I have tried:

ocean.pxd

cdef extern from "ocean.hpp":
cdef cppclass Ocean:
    Ocean()

test.pyx

from ocean cimport Ocean

cdef extern from "testclass.hpp":
    cdef cppclass TestClass:
        TestClass()
        int x
        int y
        int Multiply(int a, int b)

cdef class pyTestClass:
    cdef TestClass* thisptr # hold a C++ instance

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

    def __dealloc__(self):
        del self.thisptr

    def Multiply(self, a, b):
        return self.thisptr.Multiply(a, b)

setup.py

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

ext = Extension("test",
                sources=["test.pyx", "testclass.cpp"],
                language="c++")

setup(name="test",
      ext_modules=cythonize(ext))

Q1) Is the right way of doing it ? (When I compile this I get the following error and I don't understand the error. )

C:\MinGW\bin\gcc.exe -mdll -O -Wall -IC:\Python27\include -IC:\Python27\PC -c testclass.cpp -o build\temp.win32-2.7\Release\testclass.o
writing build\temp.win32-2.7\Release\test.def
C:\MinGW\bin\g++.exe -shared -s build\temp.win32-2.7\Release\test.o build\temp.win32-2.7\Release\testclass.o build\temp.win32-2.7\Release\test.def -LC:\Python27\libs -LC:\Python27\PCbuild -LC:\Python27\PC\VS9.0 -lpython27 -lmsv
cr90 -o E:\00-Projets\InWave\Couplage\PYW\C++\test3\test.pyd
build\temp.win32-2.7\Release\testclass.o:testclass.cpp:(.text+0x97): undefined reference to `Ocean::~Ocean()'
build\temp.win32-2.7\Release\testclass.o:testclass.cpp:(.text+0xa3): undefined reference to `Ocean::~Ocean()'
build\temp.win32-2.7\Release\testclass.o:testclass.cpp:(.text+0xe4): undefined reference to `Ocean::Ocean()'
build\temp.win32-2.7\Release\testclass.o:testclass.cpp:(.text+0xfa): undefined reference to `Ocean::Ocean()'
build\temp.win32-2.7\Release\testclass.o:testclass.cpp:(.text+0x11a): undefined reference to `Ocean::~Ocean()'
build\temp.win32-2.7\Release\testclass.o:testclass.cpp:(.text+0x196): undefined reference to `Ocean::~Ocean()'
collect2.exe: error: ld returned 1 exit status
error: command 'C:\\MinGW\\bin\\g++.exe' failed with exit status 1

Upvotes: 1

Views: 281

Answers (1)

SGirardin
SGirardin

Reputation: 31

I finally manage to make it work.

There is no need to wrap the Ocean class and consequently to create a .pxd file for it. But in setup.py it is important to include all .cpp dependancies.

As said in previous comments it is also important to add include guard in headers file.

So here is a working code (compiled with python setup.py build_ext --inplace)

ocean.hpp

#ifndef OCEAN_H
#define OCEAN_H

class Ocean {
    public:
        double _depth;
        double _rho;

        Ocean();
        virtual ~Ocean();
        void setwaterdepth(double d);
};

#endif

testclass.hpp

#ifndef TESTCLASS_H
#define TESTCLASS_H
#include "ocean.hpp"

class TestClass {
    private:
        Ocean _ocean;

    public:
        int x, y;
        TestClass();
        virtual ~TestClass();
        int Multiply(int a, int b);
        void _set_x(int x);
};

#endif

testclass.cpp

#include <iostream>
#include "testclass.hpp"
#include "ocean.hpp"

TestClass::TestClass()
{
    x = 5;
    y = 1;
    _ocean = Ocean();
    std::cout << "Calling constructor" << std::endl;
}

TestClass::~TestClass()
{
    std::cout << "Calling destructor" << std::endl;
}

int TestClass::Multiply(int a, int b)
{
    return a*b;
}

void TestClass::_set_x(int new_x)
{
    x = new_x;
}

test.pyx

cdef extern from "testclass.hpp":
    cdef cppclass TestClass:
        TestClass()
        int x
        int y
        int Multiply(int a, int b)

cdef class pyTestClass:
    cdef TestClass* thisptr # hold a C++ instance

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

    def __dealloc__(self):
        del self.thisptr

    def Multiply(self, a, b):
        return self.thisptr.Multiply(a, b)

    property y:

        # Here we use a property to expose the public member
        # y of TestClass to Python

        def __get__(pyTestClass self):
            return self.thisptr.y

        def __set__(pyTestClass self, value):
            self.thisptr.y = <int> value

setup.py

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

ext = Extension("test",
            sources=["test.pyx", "testclass.cpp", "ocean.cpp"],
            language="c++")

setup(name="test", ext_modules=cythonize(ext))

test of the wrapping

import test as wrapper

T = wrapper.pyTestClass()
print T.Multiply(3, 5)
print T.y
T.y = 3
print T.y

outputs

Calling ocean constructor
Calling ocean constructor
Calling ocean destructor
Calling constructor
15
1
3
Calling destructor
Calling ocean destructor

Upvotes: 2

Related Questions