将Python函数传递给SWIG封装的C++代码

4
我将尝试使用SWIG将C++库包装为Python库。该库经常使用回调函数,通过将某些类型的回调函数传递给类方法来实现。
现在,在包装代码之后,我想从Python中创建回调逻辑。这是否可能?以下是我进行的实验,目前无法正常工作。
头文件和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包装器接受的类型?


遗憾的是,在http://www.swig.org/Doc2.0/SWIGDocumentation.html#SWIG_nn30中提到:“只要回调函数在C中定义而不是目标语言中定义,SWIG就可以完全支持函数指针”,所以我想这是不可能的。不过,如果有关于如何从Python实现回调逻辑的任何想法,我们将不胜感激。 - El Sampsa
绝对不是不可能的,我们可以定制一切,但既然您正在使用C++而不是C,为什么不使用std::function呢?这使得编写更清晰的答案变得更简单 - 如果您满意这个更改,我将为您编写完整的解决方案。(本质上是Python版本的https://dev59.com/Y1wY5IYBdhLWcg3waXO5#32668302,或者是https://dev59.com/9mbWa4cB1Zd3GeqPUDHZ#11522655的通用版本) - Flexo
如果失败了,真正的回调函数会得到一个void*参数,你在注册它们时可以设置。这样会让生活更加简洁。 - Flexo
我遇到了同样的问题,你能否展示一下你的最终解决方案? - Xingjun Wang
在我的最终解决方案中,我决定不将Python函数传递到cpp级别,而是在cpp级别完成所有操作。 :) - El Sampsa
2个回答

4
似乎将ctypes和SWIGtypemap组合使用是解决问题最简单的方法。 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) 一个名为 use_callback 的 Python 函数,它使用 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文件的完整示例
编辑:将Python部分移动到SWIG文件的%pythoncode块中,以融入@Flexo建议。
编辑:融入@user87746的建议,以实现Python 3.6+兼容性。

1
我喜欢这样使用ctypes。我认为你可以通过将所有的ctypes部分隐藏在%pythoncode %{ ... %}块中,使SWIG/Python部分更加简洁,这样从Python模块的用户的角度来看,他们只需调用带有Python可调用对象的use_callback函数,其他所有操作都在幕后进行。 - Flexo
@Flexo,好建议,我已经将其纳入示例中。 - sterin
一个后续问题:在“f = py_callback_type(py_callback)”发生了什么..?Python代码是否会转换为机器代码或类似的东西..?这个东西在底层是如何工作的? - El Sampsa
Ctypes模块使用libffi来实现此功能。从维基百科页面可知:libffi可以生成一个指针,指向一个可以接受和解码运行时定义的任何参数组合的函数。 - sterin
1
只是一个提示,对于Python 3.6,您需要使用类型转换:PyLong_AsVoidPtr而不是PyInt_AsLong - hmaarrfk
谢谢@user87746,我已经采纳了你的建议。 - sterin

3
您可以使用 "directors" 在Python中实现回调逻辑。
基本上,您不是传递回调函数,而是传递回调对象。基础对象可以在C ++中定义,并提供一个虚拟回调成员函数。然后可以从中继承该对象,并在Python中覆盖回调函数。继承的对象可以传递给C ++函数,而不是回调函数。为使其工作,您需要为此类回调类启用导演功能。
但这确实需要更改底层的C ++库。

感谢您告诉我关于这些导演的事情。到目前为止,我发现它们非常有用。但是我仍然不知道它们如何工作!Python代码是否在某个阶段被“编译”,并传递给C语言,或者Swig是否将Python“运行时环境”嵌入包装的C代码中(即是否从C中执行“PyRun_SimpleString”等)。我感到困惑。 - El Sampsa
我只知道 SWIG 手册中所说的那些。我确定没有 Python 代码被编译。但是显然从 C 中调用了 Python 代码。这是否需要嵌入 Python 运行时环境? - m7thon
嗯...我猜它在包装器中调用了Python C API的PyObject_CallMethod函数。 - El Sampsa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接