Search code examples
c++boostboost-python

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.


Solution

  • 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