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

标签 python c++ swig

我有一个 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)

最佳答案

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

在接口(interface)文件中,我包含了后端代码 (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,PyIntint,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

关于python - 如何为 Python Swigged C++ 对象创建和分配回调函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51371218/

相关文章:

c++ - 如何调试解决方案中包含的非托管 C++ 代码

python - 如何找到一个投影来保留内积的相对值?

python - 在 Python 3.4 中使用 Kivy

python - 最Pythonic 的Tic-Tac-Toe 游戏板表示?

c++ - 全局环境照明?

C++ 类运算符重载

c++ - 使用模板基类消除工厂类派生类冗余的简洁方法

python - 使用 CMake 打包和安装 python 绑定(bind)

python - 循环输入目录名称(字符串错误)

python /痛饮 : Output an array