[C++-sig] Re: Getting Python Object for C++ object

abeyer at uni-osnabrueck.de abeyer at uni-osnabrueck.de
Fri Aug 15 10:58:13 CEST 2003


>> Eventually I would like to call peer-objects with 'self' as an argument
>> from within C++ as well as from python. (BTW the latter is quite simple.)
>> My first attempt was the following:
>>
>> class A : public enable_shared_from_this<A> {
>>   public:
>> 	virtual python::object get_self() {
>> 		shared_ptr<A> self = shared_from_this();
>> 		assert(self != 0);
>> 		return python::object(self.get());
>                               ^^^^^^^^^^
> This will trip you up.  Boost.Python can't convert a raw pointer into
> an existing Python object; when you explicitly convert a pointer to
> Python it creates a new Python object and copies the pointee.  You want
>
>   		return python::object(self);
>
> instead.

That works. Find below an example. Note, that I am referencing the
'_internal_weak_this' in order to find out if I actually have a shared
pointer for this. (I do not want to wait for the bad_weak_ptr exception.)
Dave, I am not on the boost developer list. Could you suggest the
following extension to enable_shared_from_this:

bool has_shared_from_this()
{
     return ! _internal_weak_this.expired();
}

Find following a description of the problem and its solution, which may go
into FAQ and/or a respective tutorial, if you like.

Assume, you have written a class 'A' in C++, which has an update function
for updating peer objects. The update function takes another object and
calls its 'hello()' method with self as an argument. This is typically
used in event handling mechanisms.

>>> from self_test import *
>>> a=A()
>>> a
<self_test.A object at 0x008F7750>
>>> class B:
...     def hello(self, other):
...             print self, 'got called from', other
...
>>> b=B()
>>> a.update(b)
<__main__.B instance at 0x008FAE40> got called from <self_test.A object at
0x008F7750>
>>>

Crucial is, that 'a' passes its original python object to 'b' and not a
copy. This is relatively simple to accomplish for calls from python,
because the python object for 'a' is easily accessible in that case. A
possible implementation in C++ looks like the following:

  static void call_from_py(python::object self, PyObject* other) {
     python::call_method<void>( other, "hello", self );
  }

The trick is to define call_from_py() static and to explicitely get the
reference to the called object ('self'). The boost.python wrapping for
that function is not at all fancy:

   class_<A>("A" )
       .def("update", &A::call_from_py)
   ;

However, what if we want to call A's update function from within C++? In
that case the python object is not (directly) accesible. In that case we
can make use of shared pointers. boost.python creates shared pointers for
each python object. If we pass the right shared pointer to a
python::object constructor boost.python can find an existing python object
and return a reference to that object. Thus, we are left with finding 'the
right shared pointer'. With the public enable_shared_from_this<> template
we can get an _existing_ shared pointer from within a C++ object. However,
such pointer must be available.

class A : public enable_shared_from_this<A> {
  public:
    typedef shared_ptr<A> A_ptr;
    virtual A_ptr self() {
	    if( _internal_weak_this.expired() )
		throw RuntimeError("Shared pointer not available.");
	    shared_ptr<A> self = shared_from_this();
	    assert(self != 0);
	    return self;
    }
    void call_from_cpp(PyObject* other) {
       python::call_method<void>( other, "hello", python::object(self()) );
    }
    virtual ~A() {}
}

In order to test this, we create another class 'C', which stores instances
of 'A' and calls their 'call_from_cpp()' methods:

class C {
     private:
	typedef A::A_ptr A_ptr;
     public:
	void set(A_ptr a) { this->a=a; } // store instance
	void update(PyObject* peer) { if(a.get()) a->call_from_cpp(peer); }
     private:
	A_ptr a;
};

Note that C.set() takes an A_ptr as argument. Now, we can do the following:

>>> a.self()  # No A_ptr has been created, yet.
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
RuntimeError: Shared pointer not available.
>>> c=C()
>>> c.set(a)  # This creates the A_ptr
>>> a.self()  # Ahh! The same as 'a'!
<self_test.A object at 0x008F7750>
>>> c.update(b)
<__main__.B instance at 0x008FAE40> got called from <self_test.A object at
0x008F7750>
>>>

And here is the complete example file:

--- self_test.cpp ---

/* How to get the python object for a raw pointer?
 *
 * Andreas Beyer
 */

#include <assert.h>
#include <iostream>
#include <string>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/python.hpp>

using namespace boost;

struct RuntimeError {
    RuntimeError(std::string msg) : message(msg) { }
    const char *what() const throw() { return message.c_str(); }
    std::string message;
};

void runtime_error(RuntimeError const &x) {
      PyErr_SetString(PyExc_RuntimeError, x.what());
}

class A : public enable_shared_from_this<A> {
  public:
    typedef shared_ptr<A> A_ptr;

    /* Here 'a' tries to get the shared_ptr representing the
     * python object from boost::python.
     * Certainly this only works after a shared pointer has
     * actually been created, e.g. by adding 'a' to a container
     * of type C. After that, shared_from_this() actually returns
     * a valid shared_ptr, which can be used for getting the
     * respective python::object.
     */
    virtual A_ptr self() {
	if( _internal_weak_this.expired() )
		throw RuntimeError("Shared pointer not available.");
	shared_ptr<A> self = shared_from_this();
	assert(self != 0);
	return self;
    }
    /* This is how you send a message to another python object
     * with self as an argument:
     * Make the function static and pass the implicit argument 'self'.
     * Unfortunately this only works if call_from_py() is called
     * from the python interpreter. Otherwise, the python object is
     * not available. (Except of course if the calling instance has
     * a reference to the python object stored somewhere.)
     */
    static void call_from_py(python::object self, PyObject* other) {
         python::call_method<void>( other, "hello", self );
    }

    /* This function allows you to call the peer without (explicitely)
     * knowing the python::object of the sender. However, it requires
     * that a shared pointer for 'a' has been created before (e.g. by
     * adding 'a' to a container 'c'. Otherwise, 'a' cannot find its
     * corresponding A_ptr.
     */
    void call_from_cpp(PyObject* other) {
      python::call_method<void>( other, "hello", python::object(self()) );
    }

    virtual ~A() {};
};

class C {
  private:
    typedef A::A_ptr A_ptr;
  public:
    void set(A_ptr a) { this->a=a; } // store instance
    A_ptr get() { return this->a; }
    // An example, using the python object for 'a' from within C++.
    void update(PyObject* peer) { if(a.get()) a->call_from_cpp(peer); }
  private:
    A_ptr a;
};

BOOST_PYTHON_MODULE(self_test)
{
    using namespace boost::python;
    register_exception_translator<RuntimeError>(&::runtime_error);

    class_<A>("A" )
	.def("self",   &A::self)
	.def("update", &A::call_from_py)
    ;
    class_<C>("C" )
	.def("set",    &C::set )
        .def("get",    &C::get )
	.def("update", &C::update)
    ;
}

--- end ---





More information about the Cplusplus-sig mailing list