简体   繁体   English

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

[英]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. 我加了点( PyFloatdouble ,PyInt to诠释, 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);
    }
};

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.

相关问题 如何使用派生的Python Swigged对象访问C ++基类函数? - How to access C++ baseclass functions using a derived Python swigged object? 如何在C ++中分配在python中创建的对象? - How to assign in c++ an object created in python? 如何将 C++ function 作为回调传递给 Python? - How to pass C++ function as callback to Python? 来自 C++ 的回调 python 函数,对象被破坏 - callback python function from C++ , object destructed Is it possible to assign an C++ pointer to an attribute of a Python object, and retrieve it as a C++ pointer in another C++ function? - Is it possible to assign an C++ pointer to an attribute of a Python object, and retrieve it as a C++ pointer in another C++ function? 导入错误:在 python 中导入 swigged c++ 类时未定义的符号 - ImportError: undefined symbol when importing swigged c++-class in python 如何创建一个可以接收c ++ struct / instance或python对象作为参数的公共cython函数? - How to create a public cython function that can receive c++ struct/instance or python object as parameter? 如何创建和使用存储为 c++ object 的 python object? - How to create and use a python object stored as a c++ object? 如何在python子类中将重写的父类函数分配为回调函数 - How to assign an overridden parent class function as a callback function in python subclass C ++扩展Python:如何确定C回调上的python函数名称 - Extension Python by C++: How to determine python function name on C callback
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM