如何使用SWIG包装cthon函数,该函数在python中接收函数指针

时间:2014-04-07 21:43:27

标签: python c++ swig

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

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

现在这些功能的作用并不重要。重要的是myfun接受一个函数指针。

在我的接口文件中包含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接口文件中需要一个typemap声明,但我不确定正确的语法是什么或放在哪里。我试过了

%typemap double f(double);

但这不起作用。有什么想法吗?

1 个答案:

答案 0 :(得分:13)

注意:这个答案有很长的关于解决方法的部分。如果您只是想直接使用此跳过解决方案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;
}

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

这样,第一个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)的调用不会起作用。 / p>

解决方案

那么问题是我们如何(以及在​​何处)解决这个问题?事实上,我们可能会选择至少四种可能的解决方案,具体取决于您定位的其他语言数量以及&#34; Pythonic&#34;你想成为。

解决方案1:

第一个解决方案是微不足道的。我们已经使用test.make_fptr()向函数指针f返回一个Python句柄。所以我们可以打电话:

f=test.make_fptr()
test.myfun(f)

就我个人而言,我不太喜欢这个解决方案,它不是Python程序员所期望的,也不是C程序员所期望的。唯一可行的是实施的同时性。

解决方案2:

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

所以我们可以修改我们的SWIG接口文件:

%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

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

解决方案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界面中编写一些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和包装器实例创建,但我们无法真正摆脱对在我们将这些包装器传递回SWIG的任何地方都使用%feature("pythonprepend")。 (如果可以透明地做到这一点,它远远超出我的Python知识)。

解决方案4:

以前的解决方案有点整洁,它可以像你期望的那样透明地工作(作为C和Python的用户),并且它的机制只用Python来实现它。

除了需要对函数指针的每一次使用使用pythonprepend之外,还有一个问题 - 如果你运行swig -python -builtin它只是不能工作,因为没有Python代码首先放在首位! (您需要将包装器的结构更改为:f = f_wrapper(_test._f_call, _test._f_ptr),但这不够。)

因此,我们可以通过在SWIG界面中编写一些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*个用户数据参数以及回调的实际输入。 &#34; userdata&#34;在这些情况下可以是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:

我正在开发一个更简洁的第5个解决方案,它将使用%typemap(constcode)来允许%constant同时用作方法和函数指针。事实证明,虽然SWIG已经支持SWET,但我在阅读一些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接口的示例。

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