1

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.

1
  • I suggest you trim your python sample, and remove the dependency on pika, since it's not really related to the fundamental question. Then remove those two tags. Commented Nov 2, 2016 at 17:08

1 Answer 1

1

Commentary

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")();.
  • In the Python script, 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.
  • Having pointers to object as parameters to your callback handler is debatable. I think it's fine to pass them by value.

Solution

Python Script

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")

Using Plain Functions

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

Using 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
Sign up to request clarification or add additional context in comments.

1 Comment

exactly what I needed. Thanks so much.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.