简体   繁体   中英

Boost.python inheriting from wrapped classes

I have run into a problem when creating python bindings for an existing library with boost.python. The scenario is as follows:

#include<boost/python.hpp>

namespace bp = boost::python;

struct Base {
    std::stringstream _myString;
    Base() { };
    Base(const Base& base) { _myString<<base._myString.str(); }

    void getString(std::stringstream& some_string) {
        _myString.str("");
        _myString<<some_string.str();
        std::cout<<"Got string: \""<<_myString.str()<<"\""<<std::endl;
    }
};

struct BaseWrapper : Base,
                     bp::wrapper<Base>
{
    BaseWrapper() :
        Base(),
        bp::wrapper<Base>() { };

    BaseWrapper(const Base& base) :
        Base(base),
        bp::wrapper<Base>() { };

    void getString(bp::object pyObj) {
        std::string strLine = bp::extract<std::string>(pyObj);
        std::stringstream sstrLine;
        sstrLine<<strLine;
        Base::getString(sstrLine);
    }
};

struct Derived : Base
{
    Derived() : Base() { };
    Derived(const Derived& derived) : Base() { _myString<<derived._myString.str(); };
};

struct DerivedWrapper : Derived,
                        bp::wrapper<Derived>
{
    DerivedWrapper() :
        Derived(),
        bp::wrapper<Derived>() { };

    DerivedWrapper(const Derived derived) :
        Derived(derived),
        bp::wrapper<Derived>() { };
};

BOOST_PYTHON_MODULE(testInheritance){
    bp::class_<BaseWrapper>("Base")
        .def("getString", &BaseWrapper::getString);

    bp::class_<DerivedWrapper, bp::bases<Base> >("Derived");
}

(Sorry for the long code block, it was the minimum example I could think of.)

You can see that I had to override getString() method in the BaseWrapper so that it would work with Python strings and this part works fine:

>>> import testInheritance
>>> base = testInheritance.Base()
>>> base.getString("bla")
Got string: "bla"
>>>

The problem appears as soon as I try to call getString from a instance of Derived :

>>> derived = testInheritance.Derived()
>>> derived.getString("bla")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Boost.Python.ArgumentError: Python argument types in
    Base.getString(Derived, str)
did not match C++ signature:
    getString(BaseWrapper {lvalue}, boost::python::api::object)
>>>

I can understand what is going wrong here, but I have no idea how to fix that. I would appreciate any help!

Best Regards, eDude

The problem is, that DerivedWrapper has no relationship to BaseWrapper . Thus DerivedWrapper needs to provide its own python adapted implementation of void getString(bp::object pyObj) .

So one way to make it work is like this:

struct DerivedWrapper : Derived,
                        bp::wrapper<Derived>
{
    DerivedWrapper() :
        Derived(),
        bp::wrapper<Derived>() { };

    DerivedWrapper(const Derived derived) :
        Derived(derived),
        bp::wrapper<Derived>() { };

    void getString(bp::object pyObj) {
        std::string strLine = bp::extract<std::string>(pyObj);
        std::stringstream sstrLine;
        sstrLine<<"(from DerivedWrapper) "<<strLine;
        Derived::getString(sstrLine);
    }
};

[...]

    bp::class_<DerivedWrapper, bp::bases<Base> >("Derived")
        .def("getString", &DerivedWrapper::getString);

And the output of

base = testInheritance.Base()
base.getString("bla")

derived = testInheritance.Derived()
derived.getString("blub")

is as expected

Got string: "bla"
Got string: "(from DerivedWrapper) blub"

I got exactly the same problem which I manage to solve, but solution has some internal faults in some special cases. There is a known problem about using boost::shared_ptr in python when it is necessary to create boost::weak_ptr references from values passed by boost::python. I won't go into details while it is not related to the post. Anyway I needed to wrap boost::shared_ptr into another class (I called it PythonSharedPtr) to hide boost::shared_ptr from boost::python and then I get similar problem. Consider following setup: class A is used on c++ side as boost::shared_ptr, class B (inherited from A) as boost::shared_ptr and in python both shared_ptr are wrapped into another class (for further info why I needed to make it, is explained at: boost::python and weak_ptr : stuff disappearing http://mail.python.org/pipermail/cplusplus-sig/2009-November/014983.html

So then I need to write proper export to the boost python: class_, wrapped_shared_ptr, noncopyable> and class_, wrapped_shared_ptr, noncopyable>

I think till now it is similar with your code. The tricky part is how to allow in boost::python usage of bases for the exporting of B (while shared_ptr is not related to shared_ptr). After some research in boost::python sources and came with the solution which is pretty nice, but it works only in cases, that there is no multi-inheritance for class B.

Here is the code:

namespace boost { namespace python { namespace objects
{
    template<typename Source, typename Target>
    struct shared_ptr_cast_generator
    {
        static void* execute(void* source)
        {
            const boost::shared_ptr<Source>* sourcePtr = static_cast<boost::shared_ptr<Source>*>(source);
            const boost::shared_ptr<Target> target     = boost::dynamic_pointer_cast<Target>(*sourcePtr);
            if(reinterpret_cast<size_t>(target.get()) == reinterpret_cast<size_t>(sourcePtr->get()))
                return source;
            else
            {
                // assertion which is triggered when multi-inheritance is used for Source type
                // in this case it is necessary to create new instance of shared_ptr<Target> but
                // it is not possible to make it in-place due to memory leak
                assert(!"Wrong cast");
                return nullptr;
            }
        }
    };

    template<typename Source, typename Target>
    struct cast_generator<boost::shared_ptr<Source>, boost::shared_ptr<Target> >
    {
        typedef shared_ptr_cast_generator<Source, Target> type;
    };
}}}

By providing such code, it is possible to adjust second export to the python: class_, wrapped_shared_ptr, noncopyable, bases >

Just be carefull with the conversion stored in execute function - if conversion between Source and Target exists, it returns same address - so it must be also valid if you just reinterpret Source* to Target* (data in both classes must be stored at exactly same places).

Maybe such solution is not sufficient in your case, but at least it can give you some ideas.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM