I have a method exported to Python using boost python that takes a boost::functio开发者_如何学JAVAn as an argument.
From what I have read boost::python should support boost::function without much fuss, but when I try to call the function with a python method it gives me this error
Boost.Python.ArgumentError: Python argument types in
Class.createTimer(Class, int, method, bool)
did not match C++ signature:
createTimer(class Class {lvalue}, unsigned long interval,
class boost::function<bool _cdecl(void)> function, bool recurring=False)
I am calling it from python with this code
self.__class.createTimer( 3, test.timerFunc, False )
and in C++ it is defined as
boost::int32_t createTimer( boost::uint32_t interval, boost::function< bool() > function, bool recurring = false );
The goal here is a timer class where I can do something like
class->createTimer( 3, boost::bind( &funcWithArgs, arg1, arg2 ) )
to create a timer that executes the funcWithArgs. Thanks to boost bind this will work with pretty much any function or method.
So what is the syntax I need to use for boost::python to accept my python functions as a boost::function?
Got an answer on the python mailing list, and after a bit of reworking and more research I got exactly what I wanted :)
I did see that post before mithrandi but I did not like the idea of having to declare the functions like that. With some fancy wrappers and a bit of python magic this can work and look good at the same time!
To start, wrap up your python object with code like this
struct timer_func_wrapper_t
{
timer_func_wrapper_t( bp::object callable ) : _callable( callable ) {}
bool operator()()
{
// These GIL calls make it thread safe, may or may not be needed depending on your use case
PyGILState_STATE gstate = PyGILState_Ensure();
bool ret = _callable();
PyGILState_Release( gstate );
return ret;
}
bp::object _callable;
};
boost::int32_t createTimerWrapper( Class* class, boost::uint64_t interval, bp::object function, bool recurring = false )
{
return class->createTimer( interval, boost::function<bool ()>( timer_func_wrapper_t( function ) ), recurring );
}
when in your class define the method like so
.def( "createTimer", &createTimerWrapper, ( bp::arg( "interval" ), bp::arg( "function" ), bp::arg( "recurring" ) = false ) )
With that little bit of wrapper you can work magic like this
import MyLib
import time
def callMePls():
print( "Hello world" )
return True
class = MyLib.Class()
class.createTimer( 3, callMePls )
time.sleep( 1 )
To mimic the C++ completely, we also need a boost::bind implementation which can be found here: http://code.activestate.com/recipes/440557/
With that, we can now do something like this
import MyLib
import time
def callMePls( str ):
print( "Hello", str )
return True
class = MyLib.Class()
class.createTimer( 3, bind( callMePls, "world" ) )
time.sleep( 1 )
EDIT:
I like to follow up on my questions when I can. I was using this code successfully for a while but I found out that this falls apart when you want to take boost::function's in object constructors. There is a way to make it work similarly to this but the new object you construct ends up with a different signature and will not work with other objects like itself.
This finally bugged me enough to do something about it and since I know more about boost::python now I came up with a pretty good 'fits all' solution using converters. This code here will convert a python callable to a boost::python< bool() > object, it can be easily modified to convert to other boost functions.
// Wrapper for timer function parameter
struct timer_func_wrapper_t
{
timer_func_wrapper_t( bp::object callable ) : _callable(callable) {}
bool operator()()
{
return _callable();
}
bp::object _callable;
};
struct BoostFunc_from_Python_Callable
{
BoostFunc_from_Python_Callable()
{
bp::converter::registry::push_back( &convertible, &construct, bp::type_id< boost::function< bool() > >() );
}
static void* convertible( PyObject* obj_ptr )
{
if( !PyCallable_Check( obj_ptr ) ) return 0;
return obj_ptr;
}
static void construct( PyObject* obj_ptr, bp::converter::rvalue_from_python_stage1_data* data )
{
bp::object callable( bp::handle<>( bp::borrowed( obj_ptr ) ) );
void* storage = ( ( bp::converter::rvalue_from_python_storage< boost::function< bool() > >* ) data )->storage.bytes;
new (storage)boost::function< bool() >( timer_func_wrapper_t( callable ) );
data->convertible = storage;
}
};
Then in your init code, ie, BOOST_PYTHON_MODULE(), just register the type by creating the struct
BOOST_PYTHON_MODULE(Foo)
{
// Register function converter
BoostFunc_from_Python_Callable();
精彩评论