如何为Python Swigged C++对象创建和分配回调函数



我有一个swigged 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;
};

和实现代码

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

问题是,在Python中使用此类时,如何定义要传递给assignProgressEvent函数的回调函数?

以下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)

我准备了一个我在评论中提到的例子。它已经变得相当可怕,而且可能相当脆弱。在许多地方,错误检查可以得到显著改进。

在接口文件中,我包含了后端代码(test.hpp(,它或多或少是您问题中的代码,以及Python回调所需的工具。所有令人讨厌的细节都隐藏在python_callback.hpp中。

然后,我声明一个全局变量callback,它保存当前回调,以及一个调用回调的函数callback_caller。您已经注意到这种方法的一个缺点。在飞行中,任何时候最多只能有一次回调。因此,不要向一个函数传递多个回调,也不要保留对callback的引用或指针(复制可能可以,但不能保证(。

剩下的是将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

这个文件非常不完整,因为所有不同Python和C++类型之间的映射都丢失了。我以两种方式添加了一些映射(PyFloatdouble,PyInttoint, PyStringstd::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);
}
};

我们可以使用一个小脚本来测试上面的设置,希望它能打印出"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)

让我们试试

$ 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