简体   繁体   English

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

[英]How to wrap a c++ function which takes in a function pointer in python using SWIG

Here is a simplified example of what I want to do. 这是我想要做的简化示例。 Suppose I have the following c++ code in test.h 假设我在test.h中有以下c ++代码

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

It doesn't really matter for now what these functions do. 现在这些功能的作用并不重要。 The important thing is that myfun takes in a function pointer. 重要的是myfun接受一个函数指针。

After including the test.h file in my interface file, I compiled a python module "test" using SWIG. 在我的接口文件中包含test.h文件后,我使用SWIG编译了一个python模块“test”。 Now, in Python, I run the following commands: 现在,在Python中,我运行以下命令:

import test
f = test.f

This creates a properly working function f, which takes in a double. 这创建了一个正常工作的函数f,它需要一个双精度函数。 However, when I try to pass "f" into myfun within python this is what happens: 但是,当我尝试在python中将“f”传递给myfun时,会发生以下情况:

myfun(f)
TypeError: in method 'myfun', argument 1 of type 'double (*)(double)'

How do I fix this? 我该如何解决? I figure I need a typemap declaration in my SWIG interface file, but I am not sure what the proper syntax is or where to put it. 我想我的SWIG接口文件中需要一个typemap声明,但我不确定正确的语法是什么或放在哪里。 I tried 我试过了

%typemap double f(double);

but that didn't work. 但那没用。 Any ideas? 有任何想法吗?

Note: this answer has a long section on workarounds. 注意:这个答案有很长的关于解决方法的部分。 If you simply want to use this skip straight to solution 5. 如果您只是想直接使用此跳过解决方案5。

The problem 问题

You've run into the fact that in Python everything is an object. 你已经遇到这样一个事实:在Python中,一切都是一个对象。 Before we look at fixing things, first let's understand what's going on as is. 在我们考虑解决问题之前,首先让我们了解发生了什么。 I've created a complete example to work with, with a header file: 我创建了一个完整的示例,使用头文件:

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;
}

The main changes I've made so far are adding a definition to your declarations so I can test them and a make_fptr() function that returns something to Python we know is going to be wrapped as a function pointer. 到目前为止,我所做的主要更改是为声明添加一个定义,以便我可以测试它们和一个返回Python的make_fptr()函数,我们知道它将被包装为函数指针。

With this the first SWIG module might look like: 有了这个,第一个SWIG模块可能如下所示:

%module test

%{
#include "test.h"
%}

%include "test.h"

And we can compile it with: 我们可以编译它:

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

So now we can run this and ask Python about the types we have - the type of test.f and the type of the result of calling test.make_fptr()) : 所以现在我们可以运行它并向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>"

So the problem as it stands should become clear - there's no conversion from built-in functions to the SWIG type for function pointers, so your call to myfun(test.f) won't work. 所以现在的问题应该变得清晰 - 没有从内置函数到函数指针的SWIG类型的转换,所以你对myfun(test.f)调用将不起作用。

The solution 解决方案

The question then is how (and where) do we fix this? 那么问题是我们如何(以及在​​哪里)解决这个问题? In fact there are at least four possible solutions we might pick, depending on how many other languages you target and how "Pythonic" you want to be. 实际上,我们可能会选择至少四种可能的解决方案,具体取决于您定位的其他语言数量以及您希望的“Pythonic”。

Solution 1: 解决方案1:

The first solution is trivial. 第一个解决方案是微不足道的。 We already used test.make_fptr() to return us a Python handle to a function pointer for the funciton f . 我们已经使用test.make_fptr()向我们返回一个Python句柄,用于test.make_fptr() f的函数指针。 So we can infact call: 所以我们可以调用:

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

Personally I don't like this solution very much, it's not what Python programmers expect and it's not what C programmers expect. 就个人而言,我不太喜欢这个解决方案,这不是Python程序员所期望的,也不是C程序员所期望的。 The only thing going for it is the simlicity of implementation. 唯一可行的是实施的同步性。

Solution 2: 解决方案2:

SWIG gives us a mechanism to expose function pointers to the target language, using %constant . SWIG为我们提供了一种机制,可以使用%constant公开目标语言的函数指针。 (Normally this is used for exposing compile-time constants, but that's esentially all function pointers really are in their simplest form anyway). (通常这用于暴露编译时常量,但实际上所有函数指针实际上都是最简单的形式)。

So we can modify our SWIG interface file: 所以我们可以修改我们的SWIG接口文件:

%module test

%{
#include "test.h"
%}

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

%include "test.h"

The %constant directive tells SWIG to wrap f as a function pointer, not a function. %constant指令告诉SWIG将f包装为函数指针,而不是函数。 The %ignore is needed to avoid a warning about seeing multiple versions of the same identifier. 需要%ignore来避免关于查看同一标识符的多个版本的警告。

(Note: I also removed the typedef and make_fptr() function from the header file at this point) (注意:此时我还make_fptr()文件中删除了typedefmake_fptr()函数)

Which now lets us run: 现在让我们运行:

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>"

Great - it's got the function pointer. 太棒了 - 它有功能指针。 But there's a snag with this: 但是这有一个障碍:

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

Now we can't call test.f from the Python side. 现在我们不能从Python端调用test.f Which leads to the next solution: 这导致了下一个解决方案:

Solution 3: 解决方案3:

To fix this let's first expose test.f as both a function pointer and a built-in function. 为了解决这个问题我们首先揭露test.f 既是一个函数指针和一个内置的功能。 We can do that by simply using %rename instead of %ignore : 我们可以通过简单地使用%rename而不是%ignore来做到这一点:

%module test %模块测试

%{
#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>'

That's a step, but I still don't like the idea of having to remember if I should write test.f_call or just test.f depending on the context of what I want to do with f at the time. 这是一个步骤,但我仍然不喜欢必须记住我是否应该编写test.f_call或者只是test.f这取决于我当时想用f做什么的上下文。 We can achieve this just by writing some Python code in our SWIG interface: 我们可以通过在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)
%}

There are several functional bits here. 这里有几个功能位。 Firstly we create a new, pure Python class to wrap a function as both a callable and a function pointer. 首先,我们创建一个新的纯Python类,将函数包装为可调用函数和函数指针。 It holds as members the real SWIG wrapped (and renamed) function pointer and function. 它作为成员保存真正的SWIG包装(和重命名)函数指针和函数。 These are now renamed to begin with an underscore as Python convention. 这些现在被重命名为以Python下约的下划线开头。 Secondly we set test.f to be an instance of this wrapper. 其次,我们将test.f设置为此包装器的实例。 When it is called as a function it passes the call through. 当它被称为函数时,它会通过调用。 Finally we insert some extra code into the myfun wrapper to swap in the real function pointer rather than our wrapper, taking care not to alter any other arguments if there were any. 最后,我们在myfun包装器中插入一些额外的代码来交换真正的函数指针而不是我们的包装器,如果有的话,注意不要改变任何其他参数。

This does work as expected, for example with: 这确实按预期工作,例如:

import test
print "As a callable"
test.f(2.0)
print "As a function pointer"
test.myfun(test.f)

We could make this a bit nicer, for example with a SWIG macro to avoid repeating the %rename , %constant and wrapper instance creation, but we can't really escape from the need to use %feature("pythonprepend") everywhere we pass these wrappers back to SWIG. 我们可以使它更好一点,例如使用SWIG宏来避免重复%rename%constant和包装器实例创建,但是我们无法真正摆脱在我们通过的任何地方使用%feature("pythonprepend")需要这些包装回SWIG。 (If it is possible to do that transparently it's well beyond my Python knowledge). (如果可以透明地完成它,那将远远超出我的Python知识)。

Solution 4: 解决方案4:

The previous solution is somewhat neater, it works transparently as you'd expect (as both a C and Python user) and the mechanics of it is encapsulated with nothing but Python implementing it. 以前的解决方案有点整洁,它可以像你期望的那样透明地工作(作为C和Python用户),并且它的机制只用Python实现它。

There is a gotcha still though, besides the need to use pythonprepend for every single usage of the function pointers - if you run swig -python -builtin it simply won't work, because there's no Python code to prepend in the first place! 除了需要对函数指针的每一次使用都使用pythonprepend之外,还有一个问题 - 如果你运行swig -python -builtin它就行不通,因为首先没有Python代码可以预先添加! (You'd need to change the construction of the wrapper to be: f = f_wrapper(_test._f_call, _test._f_ptr) , but that won't be sufficient). (您需要将包装器的结构更改为: f = f_wrapper(_test._f_call, _test._f_ptr) ,但这还不够)。

So we can work around that by writing some Python C API in our SWIG interface: 因此,我们可以通过在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"

This is a little ugly for two reasons. 由于两个原因,这有点难看。 Firstly it uses a (thread local) global variable to store the Python callable. 首先,它使用(线程本地)全局变量来存储Python可调用。 That's trivially fixable for most real world callbacks, where there's a void* user data argument as well as the actual inputs to the callback. 对于大多数现实世界的回调来说,这是微不足道的,其中有一个void*用户数据参数以及回调的实际输入。 The "userdata" can be the Python callable in those cases. 在这些情况下,“userdata”可以是Python可调用的。

The second issue is a little more tricky to solve though - because the callable is a wrapped C function the call sequence now involves wrapping everything up as Python types and a trip up and back from the Python interpreter just to do something that should be trivial. 第二个问题虽然解决起来有点棘手 - 因为可调用是一个包装的C函数,调用序列现在涉及将所有内容包装成Python类型并从Python解释器向上和向后跳转只是为了做一些应该是微不足道的事情。 That's quite a bit of overhead. 这是相当多的开销。

We can work backwards from a given PyObject and try to figure out which function (if any) it is a wrapper for: 我们可以从给定的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"

This does need some per function pointer code, but now it's an optimisation rather than a requirement and it could be made more generic right a SWIG macro or two. 这确实需要一些每个函数指针代码,但现在它是一个优化而不是一个要求,它可以在一个或两个SWIG宏上更通用。

Solution 5: 解决方案5:

I was working on a neater 5th solution that would use %typemap(constcode) to allow a %constant to be used as both a method and a function pointer. 我正在研究一个更简洁的第5个解决方案,它将使用%typemap(constcode)来允许%constant用作方法和函数指针。 It turns out though that there's already support in SWIG for doing exatly that, which I found when reading some of the SWIG source. 事实证明,SWIG已经支持做得很好,这是我在阅读一些SWIG源时发现的。 So actually all we need to do is simply: 所以我们真正需要做的就是:

%module test

%{
#include "test.h"
%}

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

%ignore f;
%include "test.h"

The %pythoncallback enables some global state that causes subsequent functions to be wrapped as to be usable a both a function pointer and a function! %pythoncallback启用一些全局状态,导致后续函数被包装为可用作函数指针和函数! %nopythoncallback disables that. %nopythoncallback禁用它。

Which then works (with or without -builtin ) with: 然后使用以下内容(使用或不使用-builtin ):

import test
test.f(2.0)
test.myfun(test.f)

Which solves almost all the problems in one go. 它一次解决了几乎所有的问题。 This is even documented in the manual too, although there doesn't seem to be any mention of %pythoncallback . 这甚至在手册中也有记载 ,尽管似乎没有提到%pythoncallback So the previous four solutions are mostly just useful as examples of customising SWIG interfaces. 因此,前四种解决方案大多只是用作自定义SWIG接口的示例。

There is still one case where solution 4 would be useful though - if you want to mix and match C and Python implemented callbacks you would need to implement a hybrid of these two. 还有一种情况是解决方案4会很有用 - 如果你想混合和匹配C和Python实现的回调,你需要实现这两者的混合。 (Ideally you'd try and do the SWIG function pointer type conversion in your typemap and then iff that failed fallback to the PyCallable method instead). (理想情况下,您尝试在类型映射中执行SWIG函数指针类型转换,然后尝试返回到PyCallable方法失败的iff)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM