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

kch*_*462 10 c++ python swig

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

double f(double x);
double myfun(double (*f)(double x));
Run Code Online (Sandbox Code Playgroud)

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

在我的接口文件中包含test.h文件后,我使用SWIG编译了一个python模块"test".现在,在Python中,我运行以下命令:

import test
f = test.f
Run Code Online (Sandbox Code Playgroud)

这创建了一个正常工作的函数f,它需要一个双精度函数.但是,当我尝试在python中将"f"传递给myfun时,会发生以下情况:

myfun(f)
TypeError: in method 'myfun', argument 1 of type 'double (*)(double)'
Run Code Online (Sandbox Code Playgroud)

我该如何解决?我想我的SWIG接口文件中需要一个typemap声明,但我不确定正确的语法是什么或放在哪里.我试过了

%typemap double f(double);
Run Code Online (Sandbox Code Playgroud)

但那没用.有任何想法吗?

Fle*_*exo 14

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

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

有了这个,第一个SWIG模块可能如下所示:

%module test

%{
#include "test.h"
%}

%include "test.h"
Run Code Online (Sandbox Code Playgroud)

我们可以编译它:

swig2.0 -Wall -python test.i && gcc -Wall -Wextra -I/usr/include/python2.6 -std=gnu99 -shared -o _test.so test_wrap.c
Run Code Online (Sandbox Code Playgroud)

所以现在我们可以运行它并向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>"
Run Code Online (Sandbox Code Playgroud)

所以现在的问题应该变得清晰 - 没有从内置函数到函数指针的SWIG类型的转换,所以你的调用myfun(test.f)将无法工作.

解决方案

那么问题是我们如何(以及在​​哪里)解决这个问题?事实上,我们可能会选择至少四种可能的解决方案,具体取决于您定位的其他语言数量以及您希望的"Pythonic".

解决方案1:

第一个解决方案是微不足道的.我们已经习惯test.make_fptr()将函数指针返回给函数指针f.所以我们可以调用:

f=test.make_fptr()
test.myfun(f)
Run Code Online (Sandbox Code Playgroud)

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

解决方案2:

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

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

%module test

%{
#include "test.h"
%}

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

%include "test.h"
Run Code Online (Sandbox Code Playgroud)

%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>"
Run Code Online (Sandbox Code Playgroud)

太棒了 - 它有功能指针.但是这有一个障碍:

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

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

解决方案3:

为了解决这个问题我们首先暴露test.f两个函数指针和一个内置的功能.我们可以通过简单地使用%rename而不是%ignore:

%模块测试

%{
#include "test.h"
%}

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

%include "test.h"
Run Code Online (Sandbox Code Playgroud)
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>'
Run Code Online (Sandbox Code Playgroud)

这是一个步骤,但我仍然不喜欢必须记住我是应该写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)
%}
Run Code Online (Sandbox Code Playgroud)

这里有几个功能位.首先,我们创建一个新的纯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)
Run Code Online (Sandbox Code Playgroud)

我们这次可以更好一点,例如使用了一大口宏,以避免重复%rename,%constant并包装实例创建,但我们不能真正从需要逃避使用%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"
Run Code Online (Sandbox Code Playgroud)

由于两个原因,这有点难看.首先,它使用(线程本地)全局变量来存储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"
Run Code Online (Sandbox Code Playgroud)

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

解决方案5:

我正在研究一个更简洁的第5个解决方案,它将用于%typemap(constcode)允许将a %constant用作方法和函数指针.事实证明,SWIG已经支持做得很好,这是我在阅读一些SWIG源时发现的.所以我们真正需要做的就是:

%module test

%{
#include "test.h"
%}

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

%ignore f;
%include "test.h"
Run Code Online (Sandbox Code Playgroud)

%pythoncallback使得能够使随后的功能的一些全局状态被包装为可一起使用的两个函数指针和函数!%nopythoncallback禁用它.

哪个适用于(有或没有-builtin):

import test
test.f(2.0)
test.myfun(test.f)
Run Code Online (Sandbox Code Playgroud)

它一次解决了几乎所有的问题.这甚至在手册中也有记载,尽管似乎没有提及%pythoncallback.因此,前四种解决方案大多只是用作自定义SWIG接口的示例.

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