Как установить функцию 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