How to set python function as callback for c++ using pybind11?
Solution 1:
You need a little C++ to get things going. I'm going to use a simpler structure to make the answer more readable. In your binding code:
#include <pybind11/pybind11.h>
#include <functional>
#include <string>
namespace py = pybind11;
struct Foo
{
int i;
float f;
std::string s;
};
struct Bar
{
std::function<bool(const Foo &foo)> python_handler;
std::function<bool(const Foo *foo)> cxx_handler;
Bar()
{
cxx_handler = [this](const Foo *foo) { return python_handler(*foo); };
}
};
PYBIND11_MODULE(example, m)
{
py::class_<Foo>(m, "Foo") //
.def_readwrite("i", &Foo::i)
.def_readwrite("f", &Foo::f)
.def_readwrite("s", &Foo::i);
py::class_<Bar>(m, "Bar") //
.def_readwrite("handler", &Bar::python_handler);
}
Here, Foo
is the object that is passed to the callback, and Bar
is the object that needs its callback function set. Since you use pointers, I have wrapped the python_handler
function with cxx_handler
that is meant to be used in C++, and converted the pointer to reference.
To be complete, I'll give a possible example of usage of the module here:
import module.example as impl
class Bar:
def __init__(self):
self.bar = impl.Bar()
self.bar.handler = self.handler
def handler(self, foo):
print(foo)
return True
I have used this structure successfully in one of my projects. I don't know how you want to proceed, but perhaps if you don't want to change your original structure you can write wrapper classes upon them that use the given structure.
Update:
I thought that you controlled the structure when I wrote the answer above (I'll keep it for anyone who needs it). If you have a single cli
instance, you can do something like:
using Handler = std::function<bool(std::string, int, int)>;
Handler handler;
bool cb(ClientInterface *client, const Member *member, int member_num)
{
return handler(std::string(member->x), member->y, member_num);
}
// We have created cli somehow
// cli->set_callback(cb);
// cli->join();
PYBIND11_MODULE(example, m)
{
m.def("set_callback", [](Handler h) { handler = h; });
}
If you have multiple ClientInterface
instances, you can map ClientInterface
pointers to handlers and call the appropriate handler in the cb
function based on given ClientInterface
pointer.
Note: I haven't tested the above with a python script but it should work.
Another Update
If you want to handle multiple instances, the code can roughly look like this:
using Handler = std::function<bool(std::string, int, int)>;
std::map<ClientInterface *, handler> map;
bool cb(ClientInterface *client, const Member *member, int member_num)
{
// Check if <client> instance exists in map
return map[client](std::string(member->x), member->y, member_num);
}
PYBIND11_MODULE(example, m)
{
m.def("set_callback", [](int clientid, Handler h)
{
// Somehow map <clientid> to <client> pointer
map[client] = h;
});
}
Note that this isn't a runnable code and you need to complete it.