I am trying to write a wrapper Python
class for pika
in C++
. In pika
, when messages are consumed, there is a function called callback(ch, method, properties, body)
. To consume messages, you have to put the callback
function inside basic_consume
method. In my case, my callback
function resides inside C++
code because C++
handles all the necessary things, then transfer the callback back to the Consumer class. I would like to do every logic inside C++
file and leave the Python class
alone in this case.
C++:
#include <stdio.h>
#include <boost/python.hpp>
#include <boost/function.hpp>
using namespace std;
using namespace boost::python;
// boost function
void function(object *ch, object *method, object *properties, string body) {
cout << "INSIDE FUNC" << body << endl;
}
int main() {
Py_Initialize();
try {
boost::function<void(object*, object*, object*, string)> myfunc;
myfunc = boost::bind(function, _1, _2, _3, _4);
object a = import("consumer");
object b = a.attr("A");
object c = b.attr("callback")(boost::ref(myfunc));
}
catch(error_already_set const &) {
PyErr_Print();
}
return 0;
}
Python: consumer.py
import pika
class A:
def __init__(self):
cppcallback = None
self.connect()
def connect():
connection = pika.BlockingConnection(pika.ConnectionParameters("localhost"))
channel = connection.channel()
def callback(cppdosomething):
print "CALLED"
cppcallback = cppdosomething
self.start_consume()
def start_consume(self):
channel.basic_consume(cppcallback, queue="hello_world")
channel.start_consuming()
But right now, I am getting this error.
TypeError: No Python class registered for C++ class boost::function<void (boost::python::api::object*, boost::python::api::object*, boost::python::api::object*, std::string)>
Any help is appreciated.
There are several unrelated issues with your example code:
object b = a.attr("A");
-- This gets the class type. You need to run the constructor to create an instance of A
. That means a.attr("A")();
.cppcallback
is a local variable in __init__
and callback
functions. In start_consume
it's undefined. This should be a member variable, as in self.cppcallback
.object
as parameters to your callback handler is debatable. I think it's fine to pass them by value.I wrote a simplified script that mimics what you've got:
class A:
def __init__(self):
self.handler = None
def callback(self, handler):
self.handler = handler
self.do_something()
def do_something(self):
self.handler(1,2,3,"foo")
This approach is quite straightforward.
First create a callable object
using make_function
.
Then import the Python test_module
from our script, construct and instance of A
and call it's callback
member passing it our callable object as parameter.
#include <boost/python.hpp>
namespace bp = boost::python;
void callback_handler(bp::object ch
, bp::object method
, bp::object properties
, std::string const& body)
{
std::cout << "in handler: " << body << std::endl;
}
int main()
{
Py_Initialize();
try {
bp::object h = bp::make_function(callback_handler);
bp::object a = bp::import("test_module");
bp::object b = a.attr("A")(); // Construct instance of A
b.attr("callback")(h);
} catch (bp::error_already_set const &) {
PyErr_Print();
}
return 0;
}
Console output:
>example_1.exe
in handler: foo
boost::function
Using boost::function
object for the callback handler is a little trickier, since boost::function
is not supported by boost::python
by default. Thus, we first need to enable support for boost::function
, as described in this answer by Tanner Sansbury.
NB: This snippet needs to come before including boost/python.hpp
!
// ============================================================================
// Enable support for boost::function
// See https://stackoverflow.com/a/18648366/3962537
// ----------------------------------------------------------------------------
#include <boost/function.hpp>
#include <boost/function_types/components.hpp>
// ----------------------------------------------------------------------------
namespace boost { namespace python { namespace detail {
// ----------------------------------------------------------------------------
// get_signature overloads must be declared before including
// boost/python.hpp. The declaration must be visible at the
// point of definition of various Boost.Python templates during
// the first phase of two phase lookup. Boost.Python invokes the
// get_signature function via qualified-id, thus ADL is disabled.
// ----------------------------------------------------------------------------
/// @brief Get the signature of a boost::function.
template <typename Signature>
inline typename boost::function_types::components<Signature>::type
get_signature(boost::function<Signature>&, void* = 0)
{
return typename boost::function_types::components<Signature>::type();
}
// ----------------------------------------------------------------------------
}}} // namespace boost::python::detail
// ============================================================================
The rest is very similar to the first scenario.
#include <iostream>
#include <boost/python.hpp>
namespace bp = boost::python;
void callback_handler(bp::object ch
, bp::object method
, bp::object properties
, std::string const& body
, std::string const& extra)
{
std::cout << "in handler: " << body << extra << std::endl;
}
int main()
{
Py_Initialize();
try {
typedef boost::function<void(bp::object, bp::object, bp::object, std::string)> handler_fn;
handler_fn my_handler(boost::bind(callback_handler, _1, _2, _3, _4, " bar"));
bp::object h = bp::make_function(my_handler);
bp::object a = bp::import("test_module");
bp::object b = a.attr("A")();
b.attr("callback")(h);
} catch (bp::error_already_set const &) {
PyErr_Print();
}
return 0;
}
Console output:
>example_2.exe
in handler: foo bar