将 python 函数传递给 SWIG 包装的 C++ 代码

Posted

技术标签:

【中文标题】将 python 函数传递给 SWIG 包装的 C++ 代码【英文标题】:Passing python functions to SWIG wrapped C++ code 【发布时间】:2015-12-23 23:04:00 【问题描述】:

我正在尝试使用 SWIG 为 python 包装一个 C++ 库。该库经常使用回调函数,通过将某种类型的回调函数传递给类方法。

现在,在包装完代码之后,我想从 python 创建回调逻辑。这可能吗?这是我正在做的一个实验......目前不起作用。

header和swig文件如下:

paska.h:

typedef void (handleri)(int code, char* codename);

// handleri is now an alias to a function that eats int, string and returns void

void wannabe_handleri(int i, char* blah);

void handleri_eater(handleri* h);

paska.i:

%module paska

% // this section is copied in the front of the wrapper file
#define SWIG_FILE_WITH_INIT
#include "paska.h"
%

// from now on, what are we going to wrap ..

%inline %
// helper functions here

void wannabe_handleri(int i, char* blah) 
;

void handleri_eater(handleri* h) 
;

%

%include "paska.h"

// in this case, we just put the actual .cpp code into the inline block ..

最后,我在 python 中测试..

import paska

def testfunc(i, st):
  print i
  print st

paska.handleri_eater(paska.wannabe_handleri(1,"eee")) # THIS WORKS!

paska.handleri_eater(testfunc) # THIS DOES NOT WORK!

最后一行抛出“TypeError: in method 'handleri_eater', argument 1 of type 'handleri *'”

有没有办法将 python 函数“转换”为 SWIG 包装器接受的类型?

【问题讨论】:

唉,在swig.org/Doc2.0/SWIGDocumentation.html#SWIG_nn30 中,据说“只要回调函数是用 C 语言而不是目标语言定义的,SWIG 就完全支持函数指针”......所以我猜这是不可能的。任何如何从 python 执行回调逻辑的想法都值得赞赏.. 当然不是不可能,我们可以自定义所有内容,但是既然您使用的是 C++ 而不是 C,为什么不使用 std::function?这使得编写更清晰的答案变得更简单 - 如果您对更改感到满意,我会为您编写一个完整的解决方案。 (本质上是***.com/a/32668302/168175 的Python 版本,或***.com/a/11522655/168175 的通用版本) 如果真正的回调无法获得void* 参数,您可以在注册它们时设置该参数?这让生活更干净。 我也遇到了同样的问题,能否请您展示一下您的最终解决方案? 在我的最终解决方案中,我决定不将 python 函数传递到 cpp 级别,而是在 cpp 级别完成所有操作。 :) 【参考方案1】:

在我看来,ctypes 和 SWIG typemap 的组合将是解决问题的最简单方法。 ctypes 可以轻松生成调用 Python 可调用对象的 C 函数。 Python 代码应如下所示:

import example

# python callback
def py_callback(i, s):
    print( 'py_callback(%d, %s)'%(i, s) )

example.use_callback(py_callback)

在 SWIG 方面,我们有:(1) 一个 Python 函数 use_callback,它使用 ctypes 包装器包装 Python 回调,并将包装器的地址作为整数传递给 _example.use_callback(),以及 (2) 一个SWIG typemap 提取地址并将其转换为适当的函数指针。

%module example

// a typemap for the callback, it expects the argument to be an integer
// whose value is the address of an appropriate callback function
%typemap(in) void (*f)(int, const char*) 
    $1 = (void (*)(int i, const char*))PyLong_AsVoidPtr($input);;


%
    void use_callback(void (*f)(int i, const char* str));
%

%inline
%

// a C function that accepts a callback
void use_callback(void (*f)(int i, const char* str))

    f(100, "callback arg");


%

%pythoncode
%

import ctypes

# a ctypes callback prototype
py_callback_type = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)

def use_callback(py_callback):

    # wrap the python callback with a ctypes function pointer
    f = py_callback_type(py_callback)

    # get the function pointer of the ctypes wrapper by casting it to void* and taking its value
    f_ptr = ctypes.cast(f, ctypes.c_void_p).value

    _example.use_callback(f_ptr)

%

您可以通过 CMakeLists.txt 文件 here 找到此完整示例。

编辑:合并了@Flexo 建议,将 Python 部分移动到 SWIG 文件的 %pythoncode 块中。

编辑:合并了@user87746 对 Python 3.6+ 兼容性的建议。

【讨论】:

我喜欢这样使用 ctypes。我认为您可以通过将所有 ctypes 位隐藏在 %pythoncode % ... % 块中来使 SWIG/Python 稍微整洁一点,这样从 Pyhton 模块的用户的角度来看,他们只需使用 Python 可调用对象调用 use_callback,其他一切都发生在引擎盖。 @Flexo,很好的建议,我已将其合并到示例中。 一个后续问题:在“f = py_callback_type(py_callback)”处会发生什么...... python 代码是否被转换为机器代码或类似的东西......?这东西在幕后是如何工作的? ctypes 模块使用libffi 来做到这一点。来自***页面:libffi 可以生成一个指向函数的指针,该函数可以接受和解码在运行时定义的任何参数组合。 请注意,对于python 3.6,您需要使用类型转换:PyLong_AsVoidPtr 而不是PyInt_AsLong【参考方案2】:

您可以使用"directors"在Python中实现回调逻辑。

基本上,不是传递回调函数,而是传递回调对象。基础对象可以在 C++ 中定义并提供virtual 回调成员函数。然后可以继承此对象并在 Python 中覆盖回调函数。然后可以将继承的对象传递给 C++ 函数而不是回调函数。为此,您需要为此类回调类启用导演功能。

不过,这确实需要更改底层 C++ 库。

【讨论】:

感谢您告诉我有关导演的信息。现在,我发现它们非常有用。 ..但我仍然不知道它们是如何工作的..! python代码是在某个阶段“编译”并传递给C,还是swig将python“运行时环境”嵌入到包装的C代码中(即它是否从C执行“PyRun_SimpleString”等)。我很困惑。 我也只知道 SWIG 手册中所说的那么多。我确信从来没有编译过 python 代码。但是python代码显然是从C调用的,这需要嵌入python运行环境吗? hmm..我猜它在包装器中调用了python C api的PyObject_CallMethod

以上是关于将 python 函数传递给 SWIG 包装的 C++ 代码的主要内容,如果未能解决你的问题,请参考以下文章

使用 SWIG 控制器将 python 中的 numpy 函数传递给 C++

将外部指针包装到 SWIG 数据结构中

(Python) 将 Playwright Page 对象传递给包装函数的函数装饰器

SWIG (Java):如何将带有回调函数的结构从 Android 应用程序传递给 C++?

使用python包装的c ++ SWIG模拟输入

通过 SWIG 从 C++ 调用 Go 回调函数