【问题标题】:How to set python function as callback for c++ using pybind11?如何使用pybind11将python函数设置为c++的回调?
【发布时间】:2022-07-06 06:05:16
【问题描述】:
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;
    virtual 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 或者有其他方法可以做到吗?

【问题讨论】:

  • 你从哪里得到cb?您是在 C++ 代码中还是在 Python 代码中定义它?
  • @Nimrod 示例代码是用 C++ 定义的。我想在 Python 中定义 callback function 和在 Python 中定义 set_callback 以便 SDK 将调用 Python 代码中的回调函数。
  • 你见过doc吗?有用吗?
  • 描述已更新。
  • 不确定你想要什么是直接可能的。 list of builtin conversions 不包括函数指针。它包括std::function,它更加灵活。如果您可以更改 C++ 代码,请考虑将 ftype_callback 更改为 using ftype_callback = std::function<bool(ClientInterface*,const Member*,int)>; 并在 lambda 参数中使用 ftype_callback&,类似于您链接到的文档中的 func_arg 示例。

标签: python c++ pybind11


【解决方案1】:

您需要一点 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 是需要设置其回调函数的对象。由于您使用指针,因此我将 python_handler 函数与旨在用于 C++ 的 cxx_handler 包装在一起,并将指针转换为引用。

为了完整起见,我将在这里给出一个可能的模块用法示例:

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;
    });
}

请注意,这不是可运行的代码,您需要完成它。

【讨论】:

  • 感谢您的回答。我的问题是 set_callback 在 SDK 中定义为使用 function pointer。我不允许更改它。我不知道我应该如何使用你的模式来解决它。
  • 刚刚更新了我的答案以反映您的评论。
  • 请问如何将多个实例映射到不同的处理程序?我使用这种模式实现回调函数。但是,似乎set_callback 将在python 的所有线程中设置callback。多处理很好。
  • 我编辑了答案以包括映射演示。要处理多个线程,也许您可​​以为每个线程打开一个客户端或类似的东西。这完全取决于您要达到的目标。希望更新能解决您的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-19
  • 1970-01-01
  • 2022-06-16
  • 2016-02-02
  • 2021-02-23
相关资源
最近更新 更多