gpt4 book ai didi

python - 如何使用 SWIG 包装在 python 中接收函数指针的 C++ 函数

转载 作者:行者123 更新时间:2023-12-04 03:27:28 25 4
gpt4 key购买 nike

这是我想要做的一个简化示例。假设我在 test.h 中有以下 C++ 代码

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

现在这些函数做什么并不重要。重要的是 myfun 接受一个函数指针。

在我的接口(interface)文件中包含 test.h 文件后,我使用 SWIG 编译了一个 python 模块“test”。现在,在 Python 中,我运行以下命令:
import test
f = test.f

这将创建一个正常工作的函数 f,它接受一个 double 值。但是,当我尝试在 python 中将 "f"传递到 myfun 时,会发生以下情况:
myfun(f)
TypeError: in method 'myfun', argument 1 of type 'double (*)(double)'

我该如何解决?我想我的 SWIG 接口(interface)文件中需要一个 typemap 声明,但我不确定正确的语法是什么或把它放在哪里。我试过
%typemap double f(double);

但这没有用。有任何想法吗?

最佳答案

注意:这个答案有很长的部分是关于变通方法的。如果您只是想使用此方法,请直接跳至解决方案 5。

问题

您已经遇到了这样一个事实,即在 Python 中一切都是对象。在我们考虑修复问题之前,首先让我们了解正在发生的事情。我已经创建了一个完整的示例来使用头文件:

double f(double x) {
return x*x;
}

double myfun(double (*f)(double x)) {
fprintf(stdout, "%g\n", f(2.0));
return -1.0;
}

typedef double (*fptr_t)(double);
fptr_t make_fptr() {
return f;
}

到目前为止,我所做的主要更改是向您的声明添加定义,以便我可以测试它们和 make_fptr()返回一些东西给 Python 的函数我们知道将被包装成一个函数指针。

有了这个,第一个 SWIG 模块可能看起来像:
%module test

%{
#include "test.h"
%}

%include "test.h"

我们可以编译它:

swig2.0 -Wall -python test.i && gcc -Wall -Wextra -I/usr/include/python2.6 -std=gnu99 -shared -o _test.so test_wrap.c

所以现在我们可以运行它并向 Python 询问我们拥有的类型 - test.f 的类型和调用结果的类型 test.make_fptr()) :

Python 2.6.6 (r266:84292, Dec 27 2010, 00:02:40)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> type(test.f)
<type 'builtin_function_or_method'>
>>> repr(test.f)
'<built-in function f>'
>>> type(test.make_fptr())
<type 'SwigPyObject'>
>>> repr(test.make_fptr())
"<Swig Object of type 'fptr_t' at 0xf7428530>"

因此,目前的问题应该变得清晰 - 没有从内置函数到函数指针的 SWIG 类型的转换,因此您调用 myfun(test.f)不会工作。

解决方案

那么问题是我们如何(以及在​​哪里)解决这个问题?事实上,我们可能会选择至少四种可能的解决方案,这取决于您所针对的其他语言有多少以及您想成为“Pythonic”的程度。

解决方案1:

第一个解决方案是微不足道的。我们已经用过 test.make_fptr()返回一个指向函数指针的 Python 句柄 f .所以我们实际上可以调用:
f=test.make_fptr()
test.myfun(f)

我个人不是很喜欢这个解决方案,它不是 Python 程序员所期望的,也不是 C 程序员所期望的。唯一要做的就是实现的简单性。

解决方案2:

SWIG 为我们提供了一种将函数指针暴露给目标语言的机制,使用 %constant . (通常这用于公开编译时常量,但本质上所有函数指针实际上都是最简单的形式)。

所以我们可以修改我们的 SWIG 接口(interface)文件:
%module test

%{
#include "test.h"
%}

%constant double f(double);
%ignore f;

%include "test.h"
%constant指令告诉 SWIG 包装 f作为函数指针,而不是函数。 %ignore需要避免有关看到同一标识符的多个版本的警告。

(注意:此时我还从头文件中删除了 typedefmake_fptr() 函数)

现在让我们运行:

Python 2.6.6 (r266:84292, Dec 27 2010, 00:02:40)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> type(test.f)
<type 'SwigPyObject'>
>>> repr(test.f)
"<Swig Object of type 'double (*)(double)' at 0xf7397650>"

太好了 - 它有函数指针。但这有一个障碍:

>>> test.f(0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'SwigPyObject' object is not callable

现在我们无法拨打 test.f从 Python 方面。这导致了下一个解决方案:

解决方案3:

为了解决这个问题,让我们首先公开 test.f作为函数指针和内置函数。我们可以通过简单地使用 %rename 来做到这一点。而不是 %ignore :

%模块测试
%{
#include "test.h"
%}

%constant double f(double);
%rename(f_call) f;

%include "test.h"

Python 2.6.6 (r266:84292, Dec 27 2010, 00:02:40)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> repr(test.f)
"<Swig Object of type 'double (*)(double)' at 0xf73de650>"
>>> repr(test.f_call)
'<built-in function f_call>'

这是一个步骤,但我仍然不喜欢必须记住我是否应该写 test.f_call 的想法。或只是 test.f取决于我想处理的上下文 f当时。我们可以通过在 SWIG 接口(interface)中编写一些 Python 代码来实现这一点:
%module test

%{
#include "test.h"
%}

%rename(_f_ptr) f;
%constant double f(double);
%rename(_f_call) f;

%feature("pythonprepend") myfun %{
args = f.modify(args)
%}

%include "test.h"

%pythoncode %{
class f_wrapper(object):
def __init__(self, fcall, fptr):
self.fptr = fptr
self.fcall = fcall
def __call__(self,*args):
return self.fcall(*args)
def modify(self, t):
return tuple([x.fptr if isinstance(x,self.__class__) else x for x in t])

f = f_wrapper(_f_call, _f_ptr)
%}

这里有几个功能位。首先,我们创建一个新的纯 Python 类来将函数包装为可调用和函数指针。它将真正的 SWIG 包装(和重命名)函数指针和函数作为成员保存。这些现在重命名为以下划线开头作为 Python 约定。其次我们设置 test.f成为这个包装器的一个实例。当它作为函数被调用时,它会传递调用。最后我们在 myfun 中插入一些额外的代码包装器来交换真正的函数指针而不是我们的包装器,注意不要改变任何其他参数(如果有的话)。

这确实按预期工作,例如:
import test
print "As a callable"
test.f(2.0)
print "As a function pointer"
test.myfun(test.f)

我们可以让它更好一点,例如使用 SWIG 宏以避免重复 %rename , %constant和包装器实例创建,但我们无法真正摆脱使用 %feature("pythonprepend") 的需要我们到处将这些包装器传回 SWIG。 (如果可以透明地做到这一点,这远远超出了我的 Python 知识)。

解决方案4:

前面的解决方案更简洁一些,它按照您的预期(作为 C 和 Python 用户)透明地工作,并且它的机制被封装在 Python 实现之外。

但是仍然有一个问题,除了每次使用函数指针都需要使用 pythonprepend 之外 - 如果你运行 swig -python -builtin它根本行不通,因为首先没有要添加 Python 代码! (您需要将包装器的构造更改为: f = f_wrapper(_test._f_call, _test._f_ptr) ,但这还不够)。

所以我们可以通过在我们的 SWIG 接口(interface)中编写一些 Python C API 来解决这个问题:
%module test

%{
#include "test.h"
%}

%{
static __thread PyObject *callback;
static double dispatcher(double d) {
PyObject *result = PyObject_CallFunctionObjArgs(callback, PyFloat_FromDouble(d), NULL);
const double ret = PyFloat_AsDouble(result);
Py_DECREF(result);

return ret;
}
%}

%typemap(in) double(*)(double) {
if (!PyCallable_Check($input)) SWIG_fail;
$1 = dispatcher;
callback = $input;
}

%include "test.h"

由于两个原因,这有点难看。首先它使用(线程局部)全局变量来存储 Python 可调用。对于大多数现实世界的回调来说,这是很容易解决的,其中有一个 void*用户数据参数以及回调的实际输入。在这些情况下,“userdata”可以是 Python 可调用的。

不过,第二个问题解决起来有点棘手——因为可调用是一个封装的 C 函数,调用序列现在涉及将所有内容封装为 Python 类型,并从 Python 解释器来回跳转,只是为了做一些应该是微不足道的事情。这是相当多的开销。

我们可以从给定的 PyObject 向后工作并尝试找出它是哪个函数(如果有)的包装器:
%module test

%{
#include "test.h"
%}

%{
static __thread PyObject *callback;
static double dispatcher(double d) {
PyObject *result = PyObject_CallFunctionObjArgs(callback, PyFloat_FromDouble(d), NULL);
const double ret = PyFloat_AsDouble(result);
Py_DECREF(result);

return ret;
}

SWIGINTERN PyObject *_wrap_f(PyObject *self, PyObject *args);

double (*lookup_method(PyObject *m))(double) {
if (!PyCFunction_Check(m)) return NULL;
PyCFunctionObject *mo = (PyCFunctionObject*)m;
if (mo->m_ml->ml_meth == _wrap_f)
return f;
return NULL;
}
%}

%typemap(in) double(*)(double) {
if (!PyCallable_Check($input)) SWIG_fail;
$1 = lookup_method($input);
if (!$1) {
$1 = dispatcher;
callback = $input;
}
}

%include "test.h"

这确实需要一些每个函数指针代码,但现在它是一种优化而不是要求,并且可以通过一两个 SWIG 宏使其更通用。

解决方案5:

我正在研究一个更简洁的第五个解决方案,它将使用 %typemap(constcode)允许 %constant用作方法和函数指针。事实证明,SWIG 已经支持这样做,这是我在阅读一些 SWIG 源代码时发现的。所以实际上我们需要做的很简单:
%module test

%{
#include "test.h"
%}

%pythoncallback;
double f(double);
%nopythoncallback;

%ignore f;
%include "test.h"
%pythoncallback启用一些全局状态,导致后续函数被包装为可用于函数指针和函数! %nopythoncallback禁用那个。

然后工作(有或没有 -builtin ):
import test
test.f(2.0)
test.myfun(test.f)

一次解决几乎所有问题。这是偶 documented手册中也有,尽管似乎没有提到 %pythoncallback .所以前四种解决方案大多只是作为定制 SWIG 接口(interface)的示例有用。

但仍有一种情况下解决方案 4 会很有用 - 如果您想混合和匹配 C 和 Python 实现的回调,则需要实现这两者的混合。 (理想情况下,您会尝试在您的类型映射中进行 SWIG 函数指针类型转换,然后 iff 失败回退到 PyCallable 方法)。

关于python - 如何使用 SWIG 包装在 python 中接收函数指针的 C++ 函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22923696/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com