将 SWIG 用于以函数作为输入参数的函数

Using SWIG for functions with functions as input parameter

考虑以下最小示例:我有一个 C++ 函数 'myfun',它接受一个数字和另一个函数作为输入,returns 在 x 处求值的函数:

double myfun(double x, double (*f)(double y))
{ return f(x); }

我将此代码存储在 'test.cpp' 中。对应头文件中的代码'test.hpp'就是

double myfun(double x, double (*f)(double y));

现在,我尝试使用 SWIG 从 Python 访问 'myfun'。 'swig.i' 文件如下所示:

%module test
%{
#include "test.hpp"
%}
double myfun(double x, double (*f)(double y));

每当我尝试在 Python 中调用此函数时,例如

from test import myfun
myfun(2, lambda y: y**2)

我收到错误:

in method 'myfun', argument 2 of type 'double (*)(double)'

所以我的问题很简单:如何修改 'swig.i' 文件,以便我可以将任何(合适的 Python)函数传递给 'myfun'? (这个问题和这个 post How to wrap a c++ function which takes in a function pointer in python using SWIG' 有点关系,但不同的是这里我没有指定 C++ 中的输入函数,而是我想把它作为一个免费的 'parameter' ).

根据 documentation(强调我的):

Although SWIG does not normally allow callback functions to be written in the target language, this can be accomplished with the use of typemaps and other advanced SWIG features.

This 如果您想深入探讨该主题,建议阅读该章节。
无论如何,它似乎不是该工具的预期关键功能。
你能做的最好的可能是获得一个围绕 Python 设计的解决方案,并且不能与其他目标语言一起使用,但这通常不是 Swig 用户想要的。

请注意,您仍然可以在 C/C++ 中编写回调并将它们导出为常量,然后在目标语言中使用它们。
有关更多详细信息,请参阅上述我的文档。

在编写绑定时回调需要额外处理,这在 SWIG 中(尚)不受支持,显然没有真正的解决方法。

要点是 SWIG 是关于从 Python(和其他语言)调用 C,而不是从 C 调用 Python。

我们使用的是 SIP,它是一个更复杂的绑定生成器(但它是特定于 C++/Python 的)。 SIP 支持回调,甚至从 C++ classes.

继承 Python classes。

该工具非常强大,是 PyQt 绑定背后的引擎。

但是请注意,Python 中的可调用对象不仅仅是 C++ 中的函数,因为它可以包含上下文(它可以是闭包)。在 C++ 代码中,如:

int callF(int (*f)(int, int), int a, int b) {
    return f(a, b);
}

只能调用纯上下文无关函数,因此无法向其传递通用 Python 回调,因为没有足够的信息(唯一的解决方案是分配预编译的蹦床或在运行时生成 C++ 代码) .

如果您可以控制双方,但是解决方法是创建一个包含回调的 C class 并在 Python 中派生它。例如:

struct CBack {
    virtual int operator()(int x, int y) const {
        return x+y;
    }
    virtual ~CBack() {}
};

int callF(const CBack& f,
          int xv, int yv)
{
    return f(xv, yv);
}

然后从 CBack 派生 Python class 并传递:

class Add(CBack):
    def __call__(self, x, y):
        return x + y

class Mul(CBack):
    def __call__(self, x, y):
        return x * y

print callF(Add(), 3, 7) # will print 10
print callF(Mul(), 3, 7) # will print 21

在这种情况下,您还可以传递(包装的)lambda

def cback(f):
    class PyCBack(CBack):
        def __call__(self, x, y):
            return f(x, y)
    return PyCBack()

print callF(cback(lambda x, y: x*y), 3, 7)

换句话说,一个阻抗问题是 C++ 的函数与 Python 的函数不同(Python 的函数也可以有上下文)。

可以从 here 下载完整示例。