pseudo pseudo - 1 month ago 10
C++ Question

No python class registered for C++ class function pointer

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.

Answer

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 http://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