I am currently working with Boost.Python and would like some help to solve a tricky problem.
Context
When a C++ method/function is exposed to Python, it needs to release the GIL (Global Interpreter Lock) to let other threads use the interpreter. This way, when the python code calls a C++ function, the interpreter can be used by other threads. For now, each C++ function looks like this:
// module.cpp
int myfunction(std::string question)
{
ReleaseGIL unlockGIL;
return 42;
}
To pass it to boost python, I do:
// python_exposure.cpp
BOOST_PYTHON_MODULE(PythonModule)
{
def("myfunction", &myfunction);
}
Problem
This scheme works fine, however it implies that module.cpp
depends on Boost.Python
for no good reason. Ideally, only python_exposure.cpp
should depend on Boost.Python
.
Solution?
My idea was to play with Boost.Function
to wrap the function calls like this:
// python_exposure.cpp
BOOST_PYTHON_MODULE(PythonModule)
{
def("myfunction", wrap(&myfunction));
}
Here wrap
would be in charge of unlocking the GIL during the call to myfunction
. The problem with this method is that wrap
needs to have the same signature as myfunction
which would pretty much mean re-implementing Boost.Function
...
I would be very thankful if someone had any suggestion to this problem.
If someone is interested, I had a small issue with Tanner Sansbury's code when using his final working example. For some reason, I still had the problem he mentioned about having the wrong signature in the final generated
boost::function
:even when overloading
boost::python::detail::get_signature()
. The responsible for this wasboost::function_types::components
; it has a default template parameterClassTranform = add_reference<_>
which creates this class reference. To fix this, I simply changed thempl_signature
struct as follow:And now everything works like a charm.
If someone can confirm this is indeed the right fix, I'd be interested :)
Exposing functors as methods is not officially supported. The supported approach would be to expose a non-member function that delegates to the member-function. However, this can result in a large amount of boilerplate code.
As best as I can tell, Boost.Python's implementation does not explicitly preclude functors, as it allows for instances of
python::object
to be exposed as a method. However, Boost.Python does place some requirements on the type of object being exposed as a method:o
can be calledo(a1, a2, a3)
.boost::python::detail::get_signature()
function to obtain this meta-data. The meta-data is used internally to setup proper invocation, as well as for dispatching from Python to C++.The latter requirement is where it gets complex. For some reason that is not immediately clear to me, Boost.Python invokes
get_signature()
through a qualified-id, preventing argument dependent lookup. Therefore, all candidates forget_signature()
must be declared before the calling template's definition context. For example, the only overloads forget_signature()
that are considered are those declared before the definition of templates that invoke it, such asclass_
,def()
, andmake_function()
. To account for this behavior, when enabling a functor in Boost.Python, one must provide aget_signature()
overload prior to including Boost.Python or explicitly provide a meta-sequence representing the signature tomake_function()
.Lets work through some examples of enabling functor support, as well as providing functors that support guards. I have opted to not use C++11 features. As such, there will be some boilerplate code that could be reduced with variadic templates. Additionally, all of the examples will use the same model that provides two non-member functions and a
spam
class that has two member-functions:Enabling
boost::function
When using the preferred syntax for Boost.Function, decomposing the signature into meta-data that meets Boost.Python requirements can be done with Boost.FunctionTypes. Here is a complete example enabling
boost::function
functors to be exposed as a Boost.Python method:And its usage:
When providing a functor that will invoke a member-function, the provided signature needs to be the non-member function equivalent. In this case,
int(spam::*)(int)
becomesint(spam&, int)
.Also, arguments can be bound to the functors with
boost::bind
. For example, callingexample.times_two()
does not have to provide an argument, as21
is already bound to the functor.Custom functor with guards
Expanding upon the above example, one can enable custom functor types to be used with Boost.Python. Lets create a functor, called
guarded_function
, that will use RAII, only invoking the wrapped function during the RAII object's lifetime.The
guarded_function
provides similar semantics to the Pythonwith
statement. Thus, to keep with the Boost.Python API name choices, awith()
C++ function will provide a way to create functors.This allows for functions to be exposed which will run with a guard in a non-intrusive manner:
Additionally, the
with()
function provides the ability to deduce the function signatures, allowing the meta-data signature to be explicitly provided to Boost.Python rather than having to overloadboost::python::detail::get_signature()
.Here is the complete example, using two RAII types:
no_gil
: Releases GIL in constructor, and reacquires GIL in destructor.echo_guard
: Prints in constructor and destructor.And its usage:
Notice how multiple guards can be provided by using a container type, such as
boost::tuple
:When invoked in Python,
example.times_two(21)
produces the following output: