[英]How to create and assign a callback function for a Python Swigged C++ object
I have a swigged C++ class (acting as aa plugin with events) that carry pointers to assignable callback functions, eg 我有一个闷热的C ++类(充当具有事件的插件),带有指向可分配的回调函数的指针,例如
typedef void (*PluginEvent)(void* data1, void* data2);
class PluginWithEvents : public Plugin
{
public:
bool assignOnProgressEvent(PluginEvent pluginsProgress, void* userData1 = nullptr, void* userData2 = nullptr);
void workProgressEvent(void* data1, void* data2);
protected:
PluginEvent mWorkProgressEvent;
void* mWorkProgressData1;
void* mWorkProgressData2;
};
and implementation code 和实现代码
void PluginWithEvents::workProgressEvent(void* data1, void* data2)
{
if(mWorkProgressEvent)
{
mWorkProgressEvent(data1, data2);
}
}
bool PluginWithEvents::assignOnProgressEvent(PluginEvent progress, void* userData1, void* userData2)
{
mWorkProgressEvent = progress;
mWorkProgressData1 = userData1;
mWorkProgressData2 = userData2;
return true;
}
Question is, when using this class in Python, how do I define the callback function to be passed to the assignProgressEvent function? 问题是,在Python中使用此类时,如何定义要传递给AssignProgressEvent函数的回调函数?
The following Python code gives errors: 以下Python代码给出了错误:
NotImplementedError: Wrong number or type of arguments for overloaded
function 'PluginWithEvents_assignOnProgressEvent'.
Possible C/C++ prototypes are:
PluginWithEvents::assignOnProgressEvent(dsl::PluginEvent,void *,void *)
PluginWithEvents::assignOnProgressEvent(dsl::PluginEvent,void *)
PluginWithEvents::assignOnProgressEvent(dsl::PluginEvent)
I have prepared an example of what I mentioned in the comments. 我准备了一个我在评论中提到的例子。 It has become quite monstrous and it's probably rather fragile.
它变得非常可怕,并且可能相当脆弱。 There are a lot of places where error checking could be improved significantly.
在很多地方,可以大大改善错误检查。
In the interface file I include the backend code ( test.hpp
) which is more or less the one from your question and the tools which I need for the Python callback. 在接口文件中,我包含了后端代码(
test.hpp
),该代码或多或少是您所提问题中的内容以及我用于Python回调的工具。 All the nasty details are hidden in python_callback.hpp
. 所有令人讨厌的细节都隐藏在
python_callback.hpp
。
Then I declare a global variable callback
which holds the current callback and a function callback_caller
which calls the callback. 然后,我宣布一个全局变量
callback
保持目前的回调函数callback_caller
它调用回调。 You can already notice one of the shortcomings of this approach here. 您已经在这里注意到了这种方法的缺点之一。 There can at most be one callback in flight at any time.
随时最多可以有一个回调。 So don't pass more than one callback to a function and don't hold on to a reference or pointer to
callback
(copies might be okay, but no guarantees). 因此,不要将多个回调传递给一个函数,也不要保留对
callback
的引用或指针(复制可能没问题,但不能保证)。
The rest is typemaps to map Python functions to C++ function pointers. 其余的是将Python函数映射到C ++函数指针的类型映射。
test.i
%module example
%{
#include <iostream>
#include "test.hpp"
#include "python_callback.hpp"
PythonCallback callback;
void callback_caller(void *, void *) {
double pi = callback.call<double>("ABC", 3.14, 42);
std::cout << "C++ reveived: " << pi << '\n';
}
%}
%include <exception.i>
%exception {
try {
$action
} catch (std::exception const &e) {
SWIG_exception(SWIG_RuntimeError, e.what());
}
}
%typemap(typecheck) PluginEvent {
$1 = PyCallable_Check($input);
}
%typemap(in) PluginEvent {
callback = PythonCallback($input);
$1 = &callback_caller;
}
%include "test.hpp"
test.hpp
#pragma once
typedef void (*PluginEvent)(void *data1, void *data2);
class PluginWithEvents {
public:
bool assignOnProgressEvent(PluginEvent pluginsProgress,
void *userData1 = nullptr,
void *userData2 = nullptr) {
mWorkProgressEvent = pluginsProgress;
mWorkProgressData1 = userData1;
mWorkProgressData2 = userData2;
return true;
}
void workProgressEvent(void *data1, void *data2) {
if (mWorkProgressEvent) {
mWorkProgressEvent(data1, data2);
}
}
protected:
PluginEvent mWorkProgressEvent = nullptr;
void *mWorkProgressData1 = nullptr;
void *mWorkProgressData2 = nullptr;
};
python_callback.hpp
This file is very incomplete in the sense that mappings between all the different Python and C++ types are missing. 就所有不同的Python和C ++类型之间的映射缺失而言,该文件非常不完整。 I added a few mappings (
PyFloat
to double
, PyInt to
int , PyString
to std::string
) in both ways to give you a blueprint how to extend the code with your own mappings. 我加了点(
PyFloat
到double
,PyInt to
诠释, PyString
到std::string
)这两种方式给你一个蓝图如何将代码与自己的映射扩展。
#pragma once
#include <Python.h>
#include <stdexcept>
#include <string>
#include <type_traits>
namespace internal {
// Convert C++ type to Python (add your favourite overloads)
inline PyObject *arg_to_python(double x) { return PyFloat_FromDouble(x); }
inline PyObject *arg_to_python(int v) { return PyInt_FromLong(v); }
inline PyObject *arg_to_python(std::string const &s) {
return PyString_FromStringAndSize(s.c_str(), s.size());
}
// Convert Python type to C++ (add your favourite specializations)
template <typename T>
struct return_from_python {
static T convert(PyObject *);
};
template <>
void return_from_python<void>::convert(PyObject *) {}
template <>
double return_from_python<double>::convert(PyObject *result) {
if (!PyFloat_Check(result)) {
throw std::invalid_argument("type is not PyFloat");
}
return PyFloat_AsDouble(result);
}
template <>
int return_from_python<int>::convert(PyObject *result) {
if (!PyInt_Check(result)) {
throw std::invalid_argument("type is not PyInt");
}
return PyInt_AsLong(result);
}
template <>
std::string return_from_python<std::string>::convert(PyObject *result) {
char *buffer;
Py_ssize_t len;
if (PyString_AsStringAndSize(result, &buffer, &len) == -1) {
throw std::invalid_argument("type is not PyString");
}
return std::string{buffer, static_cast<std::size_t>(len)};
}
// Scope Guard
template <typename F>
struct ScopeGuard_impl {
F f;
ScopeGuard_impl(F f) : f(std::move(f)) {}
~ScopeGuard_impl() { f(); }
};
template <typename F>
inline ScopeGuard_impl<F> ScopeGuard(F &&f) {
return ScopeGuard_impl<F>{std::forward<F>(f)};
}
} // namespace internal
class PythonCallback {
PyObject *callable = nullptr;
public:
PythonCallback() = default;
PythonCallback(PyObject *obj) : callable(obj) { Py_INCREF(obj); }
~PythonCallback() { Py_XDECREF(callable); }
PythonCallback(PythonCallback const &other) : callable(other.callable) {
Py_INCREF(callable);
}
PythonCallback &operator=(PythonCallback other) noexcept {
if (this != &other) {
std::swap(this->callable, other.callable);
}
return *this;
}
// Function caller
template <typename ReturnType, typename... Args>
ReturnType call(Args const &... args) {
using internal::arg_to_python;
using internal::return_from_python;
using internal::ScopeGuard;
PyGILState_STATE gil = PyGILState_Ensure();
auto gil_ = ScopeGuard([&]() { PyGILState_Release(gil); });
PyObject *const result = PyObject_CallFunctionObjArgs(
callable, arg_to_python(args)..., nullptr);
auto result_ = ScopeGuard([&]() { Py_XDECREF(result); });
if (result == nullptr) {
throw std::runtime_error("Executing Python callback failed!");
}
return return_from_python<ReturnType>::convert(result);
}
};
We can test the above setup using a little script which hopefully prints “Hello World!”. 我们可以使用一个希望打印“ Hello World!”的小脚本来测试上述设置。
test.py
from example import *
p = PluginWithEvents()
def callback(a, b, c):
print("Hello World!")
print(a,type(a))
print(b,type(b))
print(c,type(c))
return 3.14
p.assignOnProgressEvent(callback)
p.workProgressEvent(None,None)
Let's try it 试试看
$ swig -c++ -python test.i
$ clang++ -Wall -Wextra -Wpedantic -std=c++11 -I /usr/include/python2.7/ -fPIC -shared test_wrap.cxx -o _example.so -lpython2.7
$ python test.py
Hello World!
('ABC', <type 'str'>)
(3.14, <type 'float'>)
(42L, <type 'long'>)
C++ reveived: 3.14
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.