如何为 Python Swigged C++ 对象创建和分配回调函数
Posted
技术标签:
【中文标题】如何为 Python Swigged C++ 对象创建和分配回调函数【英文标题】:How to create and assign a callback function for a Python Swigged C++ object 【发布时间】:2018-07-16 23:16:18 【问题描述】:我有一个 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)
【问题讨论】:
您想将 Python 函数作为回调传递,还是通过 Python 接口传递 C++ 函数指针? 在 Python 中,如果可能的话,可以很好地传递一个自定义 Python 函数? 这是可能的,我已经完成了something similar before,但是映射到C++函数指针并不好玩,并且会涉及全局静态变量(这很容易导致链接和并发问题)。 感谢您的示例。我会仔细看看。看起来相当复杂。 :) 那些投反对票并投票关闭而不发表评论的人是最好的。 【参考方案1】:我已经准备了一个我在 cmets 中提到的示例。它变得非常可怕,而且可能相当脆弱。有很多地方可以显着改进错误检查。
在接口文件中,我包含后端代码 (test.hpp
),这或多或少是您的问题中的代码以及 Python 回调所需的工具。所有令人讨厌的细节都隐藏在python_callback.hpp
中。
然后我声明一个 global 变量 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++ 类型之间的映射都丢失了。我添加了一些映射(PyFloat
到 double
,PyIntto
int, 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::stringbuffer, 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
【讨论】:
谢谢亨利,太棒了!我需要一些时间来掌握模板的东西!如果我可以让它与我的旧编译器 bcc32 一起工作,我会使用它。在示例中,回调没有任何参数。我假设它可以接受参数,并且 return_from_python 就是为此? 如果一天左右没有其他答案提交,我会将此标记为答案! @TotteKarlsson 我用更简单的 Python 对象调用、更多类型映射和返回值展示更新了我的答案。 @TotteKarlsson 顺便说一句,这在 Python 3 中不起作用,因为 C-API 在 2 和 3 之间发生了很大变化。尤其是整数都是PyLong
而不是 PyInt
和所有字符串是 unicode 而不是字节。
@TotteKarlsson 该代码也将不适用于 bcc32,因为它有no support for variadic templates and lambda expressions(并且没有初始化列表,但可以避免这些)。如果您可以使用 Clang 增强的 bcc32c,您将可以使用所有这些功能。以上是关于如何为 Python Swigged C++ 对象创建和分配回调函数的主要内容,如果未能解决你的问题,请参考以下文章