将 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++
(Python) 将 Playwright Page 对象传递给包装函数的函数装饰器