如何包装c++;函数,它在python中使用SWIG接收函数指针

如何包装c++;函数,它在python中使用SWIG接收函数指针,python,c++,swig,Python,C++,Swig,下面是我想做的一个简单例子。假设我在测试中有以下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

下面是我想做的一个简单例子。假设我在测试中有以下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);

但那没用。有什么想法吗?

注意:这个答案有一个关于变通方法的长长的章节。如果您只是想使用此选项,请直接跳到解决方案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\u 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\u test.so test\u wrap.c
现在我们可以运行这个程序并询问Python我们拥有的类型-test.f的类型和调用test.make_fptr())的结果类型:

Python2.6.6(r266:8429220010年12月27日00:02:40) [GCC 4.4.5]关于linux2 有关详细信息,请键入“帮助”、“版权”、“信用证”或“许可证”。 >>>导入测试 >>>类型(试验f) >>>报告(测试f) '' >>>类型(test.make_fptr()) >>>repr(test.make_fptr()) "" 因此,目前的问题应该变得清楚了——函数指针没有从内置函数转换为SWIG类型,因此对
myfun(test.f)
的调用将不起作用

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

解决方案1: 第一个解决方案很简单。我们已经使用
test.make_fptr()
为函数
f
返回函数指针的Python句柄。因此,我们实际上可以称之为:

f=test.make_fptr()
test.myfun(f)
我个人不太喜欢这个解决方案,它不是Python程序员所期望的,也不是C程序员所期望的。唯一值得一提的是实现的简单性

解决方案2: SWIG为我们提供了一种机制,可以使用将函数指针公开到目标语言。(通常这用于公开编译时常量,但本质上所有函数指针都是最简单的形式)

因此,我们可以修改SWIG接口文件:

%module test

%{
#include "test.h"
%}

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

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

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

现在让我们运行:

Python2.6.6(r266:8429220010年12月27日00:02:40) [GCC 4.4.5]关于linux2 有关详细信息,请键入“帮助”、“版权”、“信用证”或“许可证”。 >>>导入测试 >>>类型(试验f) >>>报告(测试f) "" 很好-它有函数指针。但这有一个障碍:

test.f(0) 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 TypeError:“SwigPyObject”对象不可调用 现在我们不能从Python端调用
test.f
。这将导致下一个解决方案:

解决方案3: 为了解决这个问题,我们首先将
test.f
公开为函数指针和内置函数。我们只需使用
%rename
而不是
%ignore
,即可做到这一点:

%模块测试

%{
#include "test.h"
%}

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

%include "test.h"
Python2.6.6(r266:8429220010年12月27日00:02:40) [GCC 4.4.5]关于linux2 有关详细信息,请键入“帮助”、“版权”、“信用证”或“许可证”。 >>>导入测试 >>>报告(测试f) "" >>>报告(测试f_调用) '' 这是一个步骤,但我仍然不喜欢这样的想法,即必须记住是编写
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)
%}
%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类,将函数包装为可调用函数和函数指针。它将真正的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的任何地方都需要使用
%特性(“pythonprepend”)
。(如果可以透明地做到这一点,这远远超出了我的Python知识范围)

解决方案4: 以前的解决方案比较简洁,它可以工作
%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"
%module test

%{
#include "test.h"
%}

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

%ignore f;
%include "test.h"
import test
test.f(2.0)
test.myfun(test.f)