Как установить функцию python в качестве обратного вызова для С++ с помощью 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, который я могу назвать clientиз динамической библиотеки в кодах С++.

      bool cb(ClientInterface* client, const Member* member ,int member_num) {
    // do something
}
cli->set_callback(cb);
cli->join();

Я хочу перенести его на привязки python, используя pybind11. Как я set_callbackв питоне?

Я видел документ и попробовал:

      PYBIND11_MODULE(xxx, m) {
    m.def("set_callback", [](xxx &self, py::function cb ){
        self.set_callback(cb);
    });
}

Код просто не скомпилировался.

Мой вопрос, как преобразовать py::functionк ftype_callbackили есть другой способ сделать это?

2 ответа

Вам нужно немного 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функция с cxx_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

Я успешно использовал эту структуру в одном из моих проектов. Я не знаю, как вы хотите поступить, но, возможно, если вы не хотите изменять исходную структуру, вы можете написать для них классы-оболочки, которые используют данную структуру.

Обновлять:

Я думал, что вы контролировали структуру, когда я писал ответ выше (сохраню для всех, кому это нужно). Если у вас один 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; });
}

Если у вас несколько экземпляров, вы можете сопоставить указатели с обработчиками и вызвать соответствующий обработчик в cbфункция на основе заданного ClientInterfaceуказатель.

Примечание. Я не проверял приведенное выше с помощью скрипта 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;
    });
}

Обратите внимание, что это не исполняемый код, и вам необходимо его завершить.

вы можете получить данные, используя типы python в pybind11

убедитесь, что у вас есть#include <pybind11/functional.h>

      // c++
using PyCallback = std::function<void(pybind11::bytearray)>;

class Haha
{
public:
    void setCallback(PyCallback& pyfn) {
        m_pyfn = pyfn;
    }
    void onDataAvaiable(char* buf, int len) {
        m_pyfn(pybind11::bytearray(buf, len));
    }
private:
    PyCallback m_pyfn;
};

PYBIND11_MODULE(haha, m) {
    pybind11::class_<Haha>(m, "Haha")
        .def("setCallback", &Haha::setCallback);
}

// python
def fn(data):
    print(data)

hahaInstance = m.Haha()
hahaInstance .setCallback(fn)
while True:
    // block to make sure hahaInstance is always running, then callback will print data
Другие вопросы по тегам