void* (void pointer) function arguments

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

void* (void pointer) function arguments

Holger Joukl

Hi,

I'm trying to wrap a C++-API that uses void* to pass around arbitrary,
application-specific stuff.

I am a bit unsure about how to work the void-pointers. A viable way seems
to wrap the original
API functions with thin wrappers that take a boost::python::object and hand
the "raw" PyObject*
into the original function:

// Expose the method as taking any bp::object and hand the "raw" PyObject
pointer to
// the void-ptr-expecting API-function
int Worker_destroy2_DestructionCallbackPtr_constVoidPtr(
    Worker* worker, DestructionCallback* cb, bp::object& closure) {
    return worker->destroy2(cb, closure.ptr());
}

The information available is then retrieved by some callback function
(which is pure virtual in the
API and needs to be overridden in Python). Before calling into Python I
then cast the void* back to
a PyObject*, make a boost::python::object from it and invoke the Python
override:

class DestructionCallbackWrap : public DestructionCallback, public
bp::wrapper<DestructionCallback> {
    virtual void callback(Worker* worker, void* closure) {
        std::cout << ">>> " << __PRETTY_FUNCTION__ << std::endl;
        // everything ending up here from Python side is a PyObject
        bp::handle<> handle(bp::borrowed(reinterpret_cast<PyObject*>
(closure)));
        bp::object closureObj(handle);
        this->get_override("callback")(bp::ptr(worker), closureObj);
        std::cout << "<<< " << __PRETTY_FUNCTION__ << std::endl;
    }
};

As I've tried to gather information about void* handling with boost.python
back and forth without
finding much (an FAQ entry seems to have existed once upon a time?) - is
this a sane approach?

Or is there some automagical void* or const void* handling in boost.python
that I am totally missing?

Any hint much appreciated,
Holger

P.S.:

Here's a full minimal example for reference:

// file void_ptr_cb.hpp

// API to wrap
class Worker;

class DestructionCallback {
public:
    DestructionCallback() {}
    virtual ~DestructionCallback() {}
    virtual void callback(Worker* worker, void* closure) = 0;
};


class Worker {
public:
    Worker() {}
    virtual ~Worker() {}

    virtual int destroy() { return 0; }
    int destroy2(DestructionCallback* cb, const void* closure=NULL) {
        std::cout << ">>> " << __PRETTY_FUNCTION__ << std::endl;
        cb->callback(this, const_cast<void*>(closure));
        std::cout << "<<< " << __PRETTY_FUNCTION__ << std::endl;
        return 0;
    }
private:
    Worker(const Worker& worker);

};



// file wrap_void_ptr_cb.cpp

#include <boost/python.hpp>
#include <iostream>
#include "void_ptr_cb.hpp"


namespace bp = boost::python;


// Helper classes needed for boost.python wrapping

class DestructionCallbackWrap : public DestructionCallback, public
bp::wrapper<DestructionCallback> {
    virtual void callback(Worker* worker, void* closure) {
        std::cout << ">>> " << __PRETTY_FUNCTION__ << std::endl;
        // everything ending up here from Python side is a PyObject
        bp::handle<> handle(bp::borrowed(reinterpret_cast<PyObject*>
(closure)));
        bp::object closureObj(handle);
        this->get_override("callback")(bp::ptr(worker), closureObj);
        std::cout << "<<< " << __PRETTY_FUNCTION__ << std::endl;
    }
};


class WorkerWrap : public Worker, public bp::wrapper<Worker> {
public:
    virtual int destroy() {
        if (bp::override f = this->get_override("destroy"))
            return f(); // *note*
        return Worker::destroy();

    }
    int default_destroy() {
        return this->Worker::destroy();
    }

};


// Expose the method as taking any bp::object and hand the "raw" PyObject
pointer to
// the void-ptr-expecting API-function
int Worker_destroy2_DestructionCallbackPtr_constVoidPtr(
    Worker* worker, DestructionCallback* cb, bp::object& closure) {
    return worker->destroy2(cb, closure.ptr());
}


BOOST_PYTHON_MODULE(void_ptr_cb)
{
    bp::class_<DestructionCallbackWrap, boost::noncopyable>
("DestructionCallback", bp::init<>())
        .def("callback", bp::pure_virtual(&DestructionCallback::callback))
    ;
    bp::class_<WorkerWrap, boost::noncopyable>("Worker")
        .def("destroy", &Worker::destroy, &WorkerWrap::default_destroy)
        .def("destroy2",
&Worker_destroy2_DestructionCallbackPtr_constVoidPtr, (bp::arg("cb"),
bp::arg("closure")=bp::object()))
    ;
};

# file Jamroot
# Run with:
# BOOST_ROOT=/var/tmp/lb54320/boost_apps/boost_1_46_1
BOOST_BUILD_PATH=/var/tmp/lb54320/boost_apps/boost_1_46_1 \
# /var/tmp/$USER/boost_apps/boost_1_46_1/bjam -d+2 toolset=gcc-4.5.1 \
#
--build-dir=/var/tmp/$USER/boost_apps/boost_1_46_1/build/py2.7/boost/1.46.1/
 cxxflags="-DDEBUG_HIGH \
# -DBOOST_PYTHON_TRACE_REGISTRY" link=shared threading=multi
variant=release void_ptr_cb
#
# get the environment variable "USER"
import os ;
local _USER = [ os.environ USER ] ;
#ECHO $(_USER) ;

local _WORKDIR = /var/tmp/$(_USER)/boost_apps ;
local _BOOST_MODULE = boost_1_46_1 ;
local _BOOST_ROOT = $(_WORKDIR)/$(_BOOST_MODULE) ;
local _BOOST_VERSION = 1.46.1 ;
#ECHO $(_BOOST_ROOT) ;


use-project boost : $(_BOOST_ROOT) ;


# Set up the project-wide requirements that everything uses the
# boost_python library from the project whose global ID is
# /boost/python.


project minimal_void_ptr_cb
        : requirements <library>/boost/python//boost_python
        <dll-path><variant>debug:$(_BOOST_ROOT)/stage/py2.7/boost/$
(_BOOST_VERSION)/debug/lib
        <dll-path><variant>release:$(_BOOST_ROOT)/stage/py2.7/boost/$
(_BOOST_VERSION)/lib
        ;


python-extension void_ptr_cb
        : # sources + // Add all files here otherwise we get undefined
symbol errors like
        wrap_void_ptr_cb.cpp

        : # requirements *
        : # default-build *
        : # usage-requirements *
        ;



#!/apps/local/gcc/4.5.1/bin/python2.7
# file test.py

import os
import sys

_USER = os.getenv("USER")
EXPATH = ('/var/tmp/%s/boost_apps/boost_1_46_1/build/py2.7/boost/1.46.1/'
          'minimal_void_ptr_cb/gcc-4.5.1/release/threading-multi' %
(_USER))
sys.path.insert(1, EXPATH)

import void_ptr_cb


class MyDestructionCallback(void_ptr_cb.DestructionCallback):
    def callback(self, worker, closure):
        print "MyDestructionCallback.callback(%s, %s, %s)" % (self, worker,
                                                              closure)

class MyDestructionCallback2(void_ptr_cb.DestructionCallback):
    def callback(self, worker, closure):
        print "MyDestructionCallback.callback2(%s, %s, %s)" % (self,
worker,
                                                              closure)
        closure.do('what?')


class SomeClass(object):
    def do(self, something=None):
        print "SomeClass.do(something=%s)" % repr(something)


md = MyDestructionCallback()
md.callback('worker', 'closure')

print
w = void_ptr_cb.Worker()
w.destroy()
w.destroy2(md, None)

print
some = SomeClass()
w = void_ptr_cb.Worker()
w.destroy()
w.destroy2(md, some)
some.do('else')

print
md2 = MyDestructionCallback2()
w = void_ptr_cb.Worker()
w.destroy()
w.destroy2(md2, SomeClass())


Test run output:

0 holger@devel .../minimal_void_ptr_cb $ ./test.py
MyDestructionCallback.callback(<__main__.MyDestructionCallback object at
0x2a6cc0>, worker, closure)

>>> int Worker::destroy2(DestructionCallback*, const void*)
>>> virtual void DestructionCallbackWrap::callback(Worker*, void*)
MyDestructionCallback.callback(<__main__.MyDestructionCallback object at
0x2a6cc0>, <void_ptr_cb.Worker object at 0x2a6cf0>, None)
<<< virtual void DestructionCallbackWrap::callback(Worker*, void*)
<<< int Worker::destroy2(DestructionCallback*, const void*)

>>> int Worker::destroy2(DestructionCallback*, const void*)
>>> virtual void DestructionCallbackWrap::callback(Worker*, void*)
MyDestructionCallback.callback(<__main__.MyDestructionCallback object at
0x2a6cc0>, <void_ptr_cb.Worker object at 0x2a6d20>, <__main__.SomeClass
object at 0x2b7710>)
<<< virtual void DestructionCallbackWrap::callback(Worker*, void*)
<<< int Worker::destroy2(DestructionCallback*, const void*)
SomeClass.do(something='else')

>>> int Worker::destroy2(DestructionCallback*, const void*)
>>> virtual void DestructionCallbackWrap::callback(Worker*, void*)
MyDestructionCallback.callback2(<__main__.MyDestructionCallback2 object at
0x2a6cf0>, <void_ptr_cb.Worker object at 0x2a6d50>, <__main__.SomeClass
object at 0x2b7730>)
SomeClass.do(something='what?')
<<< virtual void DestructionCallbackWrap::callback(Worker*, void*)
<<< int Worker::destroy2(DestructionCallback*, const void*)

Landesbank Baden-Wuerttemberg
Anstalt des oeffentlichen Rechts
Hauptsitze: Stuttgart, Karlsruhe, Mannheim, Mainz
HRA 12704
Amtsgericht Stuttgart

_______________________________________________
Cplusplus-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/cplusplus-sig
Reply | Threaded
Open this post in threaded view
|

Re: void* (void pointer) function arguments

Jim Bosch-2
On 08/15/2011 08:11 AM, Holger Joukl wrote:

>
> Hi,
>
> I'm trying to wrap a C++-API that uses void* to pass around arbitrary,
> application-specific stuff.
>
> I am a bit unsure about how to work the void-pointers. A viable way seems
> to wrap the original
> API functions with thin wrappers that take a boost::python::object and hand
> the "raw" PyObject*
> into the original function:
>

I don't see anything wrong with this approach, aside from the fact that
you have to make an explicit wrapper for all of your functions that take
void pointers.  It might be a little less safe, but you should be able
to make things more automatic by casting function pointers that accept a
void* to a signature with void* replaced by PyObject*; Boost.Python does
know how to wrap functions that take PyObject*.  For example, in the
wrapper for your "Worker" class, you'd have:

.def("destroy2",
     (int (Worker::*)(DestructionCallback*,PyObject*))&Worker::destroy,
     (bp::arg("cb"), bp::arg("closure")=bp::object())
)

I think this should do the same thing, though you may want to check that
default arguments work as expected.  Of course this could lead to big
problems if you pass around things that aren't in fact PyObject* as void
pointers in the same places, though I think your solution suffers from
this too.

There really isn't anything automatic for dealing with void pointers in
Boost.Python, because it's so template-based - it really can't learn
anything from a void pointer.

Good luck!

Jim Bosch
_______________________________________________
Cplusplus-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/cplusplus-sig
Reply | Threaded
Open this post in threaded view
|

Re: void* (void pointer) function arguments

Holger Joukl
Hi Jim,

> > I am a bit unsure about how to work the void-pointers. A viable way
seems
> > to wrap the original
> > API functions with thin wrappers that take a boost::python::object and
hand

> > the "raw" PyObject*
> > into the original function:
> >
>
> I don't see anything wrong with this approach, aside from the fact that
> you have to make an explicit wrapper for all of your functions that take
> void pointers.  It might be a little less safe, but you should be able
> to make things more automatic by casting function pointers that accept a
> void* to a signature with void* replaced by PyObject*; Boost.Python does
> know how to wrap functions that take PyObject*.  For example, in the
> wrapper for your "Worker" class, you'd have:

Just tested, it works:

BOOST_PYTHON_MODULE(void_ptr_cb)
{
    bp::class_<DestructionCallbackWrap, boost::noncopyable>
("DestructionCallback", bp::init<>())
        .def("callback", bp::pure_virtual(&DestructionCallback::callback))
    ;
    bp::class_<WorkerWrap, boost::noncopyable>("Worker")
        .def("destroy", &Worker::destroy, &WorkerWrap::default_destroy)
        .def("destroy2", (int (Worker::*)(DestructionCallback*,
PyObject*))&Worker::destroy2,
             (bp::arg("cb"), bp::arg("closure")=bp::object()))
        ;
};

So I could save the thin wrappers that get called from the Python side.

> I think this should do the same thing, though you may want to check that
> default arguments work as expected.  Of course this could lead to big
> problems if you pass around things that aren't in fact PyObject* as void
> pointers in the same places, though I think your solution suffers from
> this too.

Absolutely :).
The default args seem to work fine with your proposal, though.

> Good luck!

Thanks a lot and all the best

Holger

Landesbank Baden-Wuerttemberg
Anstalt des oeffentlichen Rechts
Hauptsitze: Stuttgart, Karlsruhe, Mannheim, Mainz
HRA 12704
Amtsgericht Stuttgart

_______________________________________________
Cplusplus-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/cplusplus-sig