如何使用 pybind11 将 python 函数设置为 c++ 的回调?
How to set python function as callback for c++ using pybind11?
typedef bool (*ftype_callback)(ClientInterface* client, const Member* member ,int member_num);
struct Member{
char x[64];
int y;
};
class ClientInterface {
public:
virtual int calc()=0;
virutal bool join()=0;
virtual bool set_callback(ftype_callback on_member_join)=0;
};
它来自 SDK,我可以在 C++ 代码中从动态库中调用 client
。
bool cb(ClientInterface* client, const Member* member ,int member_num) {
// do something
}
cli->set_callback(cb);
cli->join();
我想将它移植到 python 绑定使用 pybind11。我如何在 python 中 set_callback
?
我已经看到 doc 并尝试:
PYBIND11_MODULE(xxx, m) {
m.def("set_callback", [](xxx &self, py::function cb ){
self.set_callback(cb);
});
}
代码编译失败。
我的问题是,如何将 py::function
转换为 ftype_callback
或者有其他方法可以实现?
您需要一点 C++ 才能使事情顺利进行。我将使用更简单的结构来使答案更具可读性。在您的绑定代码中:
#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);
}
这里,Foo
是传递给回调的对象,Bar
是需要设置回调函数的对象。因为你使用指针,所以我用 cxx_handler
包装了 python_handler
函数,这意味着在 C++ 中使用,并将指针转换为引用。
为了完整起见,我将在此处给出该模块的可能用法示例:
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
我在one of my projects中成功地使用了这个结构。我不知道你想如何进行,但也许如果你不想改变你的原始结构,你可以在使用给定结构的它们上写包装器 类。
更新:
我写上面的答案时以为你控制了结构(我会保留它以供需要的人使用)。如果您只有一个 cli
实例,您可以执行以下操作:
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; });
}
如果您有多个 ClientInterface
实例,您可以将 ClientInterface
指针映射到处理程序,并根据给定的 ClientInterface
指针在 cb
函数中调用适当的处理程序。
注意:我还没有用 python 脚本测试上面的内容,但它应该可以工作。
另一个更新
如果要处理多个实例,代码大致如下:
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;
});
}
请注意,这不是可运行的代码,您需要完成它。
typedef bool (*ftype_callback)(ClientInterface* client, const Member* member ,int member_num);
struct Member{
char x[64];
int y;
};
class ClientInterface {
public:
virtual int calc()=0;
virutal bool join()=0;
virtual bool set_callback(ftype_callback on_member_join)=0;
};
它来自 SDK,我可以在 C++ 代码中从动态库中调用 client
。
bool cb(ClientInterface* client, const Member* member ,int member_num) {
// do something
}
cli->set_callback(cb);
cli->join();
我想将它移植到 python 绑定使用 pybind11。我如何在 python 中 set_callback
?
我已经看到 doc 并尝试:
PYBIND11_MODULE(xxx, m) {
m.def("set_callback", [](xxx &self, py::function cb ){
self.set_callback(cb);
});
}
代码编译失败。
我的问题是,如何将 py::function
转换为 ftype_callback
或者有其他方法可以实现?
您需要一点 C++ 才能使事情顺利进行。我将使用更简单的结构来使答案更具可读性。在您的绑定代码中:
#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);
}
这里,Foo
是传递给回调的对象,Bar
是需要设置回调函数的对象。因为你使用指针,所以我用 cxx_handler
包装了 python_handler
函数,这意味着在 C++ 中使用,并将指针转换为引用。
为了完整起见,我将在此处给出该模块的可能用法示例:
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
我在one of my projects中成功地使用了这个结构。我不知道你想如何进行,但也许如果你不想改变你的原始结构,你可以在使用给定结构的它们上写包装器 类。
更新:
我写上面的答案时以为你控制了结构(我会保留它以供需要的人使用)。如果您只有一个 cli
实例,您可以执行以下操作:
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; });
}
如果您有多个 ClientInterface
实例,您可以将 ClientInterface
指针映射到处理程序,并根据给定的 ClientInterface
指针在 cb
函数中调用适当的处理程序。
注意:我还没有用 python 脚本测试上面的内容,但它应该可以工作。
另一个更新
如果要处理多个实例,代码大致如下:
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;
});
}
请注意,这不是可运行的代码,您需要完成它。