3

My problem is as follow: I have a C++ code that I can't modify as I want (not written by me and under some intellectual property constraints). I have to link it with a Python code through a Python main. I use boost::python for the reflection. At some point in the code, a C++ function use a Python overloaded function of a C++ function to modify a parameter. This function works great when used inside Python. But when called by a C++ function, the modification of the parameter will not be taken into account while back in C++. To model the problem, I wrote a simplified version aimed at isolating my problem.

Here are the C++ classes:

#include <iostream>
using namespace std;
#include <boost/ref.hpp>

//Class aimed at modeling a C++ user's type that should be used in Python
class C
{
public:
    double myC;
    C(){}
    ~C(){}

    void setValue(double val) {
        myC = val;
    }

    double getValue() {
        return myC;
    }
};

/*
    This class will be derived in Python
    We want to be sure that:
    - void (g) and non void (f) functions are properly reflected and extended in Python
    - a function (h) with a local type (C) will properly use the python overloaded     function of a C++ function (g)
*/

class A
{
public:
    A(){int A_x=0;}
    virtual ~A(){}
    virtual int f(int Ivar){
        Ivar = 0;
        return Ivar;
    }
    virtual void g(C * myC){
        C calledC;
        cout << "I should not be here" << endl;   //When B::setparam call g, it should be the Python g, not the virtual function from base class
        calledC.setValue(0.);
        //return *myC;
    }
    C h(){                  //This function wont be overloaded in Python
        C myC;
        //myC.setValue(0.);
        this->g(&myC);      //We want myC to be modified, and the python overloaded version of g to be used
        cout << "Value back in C++: " << myC.getValue() << endl; //We want to verify if myC has been modified back in C++
        return myC;
    }
};

/*
    This class will be called from a main in Python.
    It's function setparam is aimed at ensuring that Python extension of a C++ class is supported properly
*/

class B
{
public:
    B(A & a){
        setparam(a); 
    }
    B(){
        int u;
        u = 0;
    }
    virtual ~B(){}
    void setparam(A & a){
        a.h();              //Here the code should call the h overloaded function of the derived class of A in Python
    }
};

The boost::python wrapper:

#include <boost/python.hpp>
#include <Python.h>
#include <object.h>
#include "ClassA.h"

#include <boost/python/module.hpp>
#include <boost/python/def.hpp>
#include <boost/python/implicit.hpp>
#include <iostream>
using namespace std;

using namespace boost::python;

/*
    A wrapper is used to allow overload of virtual A function
*/
struct WrapperClassA : A, boost::python::wrapper<A>{
    int f(int Ivar){
        if(override f = this->get_override("f")){
            return f(Ivar);
        }
        else {
            return A::f(Ivar);
        }
    }
    int default_f(int Ivar){
        return this->A::f(Ivar);
    }

    void g(C * myC){
        if(override g = this->get_override("g")){
                    g(myC);
        }
        else {
            A::g(myC);
        }
    }
    void default_g(C * myC){
        this->A::g(myC);
    }
};

/*
    Refection of necessary classes and functions for Python
*/
BOOST_PYTHON_MODULE(myWrapper){
    class_<WrapperClassA, boost::noncopyable>("A", init<>())
    .def("f", &A::f, &WrapperClassA::default_f)
    .def("g", &A::g, &WrapperClassA::default_g)
    ;

    class_<B, boost::noncopyable>("B", init<>())
    .def("setparam", &B::setparam)
    ;

    class_<C>("C", init<>())
    .def("setValue", &C::setValue)
    .def("getValue", &C::getValue)
    .def_readwrite("myC", &C::myC)
    ;
}

The python code:

from math import *
# C++ library
from myWrapper import *

"""
    Extension of C++ class A
    APy should be used when A type argument is called
    g should be able to use a C++ C type object and to modify it
    The constructor call g in order to verify that myC is treated as mutable within Python
"""
class APy(A):
    def __init__(self):
        A.__init__(self)
        n = 0
        x = 0.
        myC = C()
        self.g(myC)
        print("value after a 1st call within Python: " + repr(myC.getValue()))

    def f(self, n):
        n = 5
        return n

    def g(self, myC):
        x = 7.0
        myC.setValue(x)
        print("value in Python: " + repr(myC.getValue()))

"""
    Main
    myB.setparam should be able to pass a APy argument
"""
myA = APy()
myB = B()
myB.setparam(myA)

When I run the code, print("value in Python: " + repr(myC.getValue())) print 7, but cout << myC.getValue() << endl print 0 while I would like the variable to be at 7. Thank you for any help.

1 Answer 1

1

The problem here is that when you convert a C instance to Python in the call g(myC), Boost.Python uses a by-value converter rather than a shallow converter (i.e. it copies the C instance), even though you've passed a pointer.

This is by design: when you pass a C++ pointer to Python, Boost.Python would like to ensure that the Python code doesn't have to worry about other C++ code deleting it while the Python layer still has access to it, as this could lead to dangerous memory errors. The only way to ensure that is to copy the object.

If you'd like to force Boost.Python to pass the myC object by reference, use g(boost::python::ptr(myC)) (or g(boost::ref(*myC)) if you know it's not null), but be aware that this is dangerous; you need to make sure the Python implementation of g() doesn't try to hold onto its argument beyond its lifetime.

You can find more information here:

http://www.boost.org/doc/libs/1_48_0/libs/python/doc/v2/callbacks.html

You can also use boost::shared_ptr to pass arguments to Python without copying in a safe way:

boost python: how to call a C++ virtual function

Sign up to request clarification or add additional context in comments.

1 Comment

Sorry, couldn't edit my answer on time. Thank you very much for your answer. I just used g(boost::python::ptr(myC)) in the if part of the ClassWrapperA, and now it's working perfectly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.