将 python 函数传递给 SWIG 包装的 C++ 代码
Passing python functions to SWIG wrapped C++ code
我正在尝试使用 SWIG 为 python 包装一个 C++ 库。该库经常使用回调函数,通过将 某种类型的回调函数 传递给 class 方法。
现在,包装代码后,我想从 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 *'"
有什么方法可以 "cast" 将 python 函数转换为 SWIG 包装器接受的类型?
您可以使用"directors"在Python中实现回调逻辑。
基本上,不是传递回调函数,而是传递回调对象。基础对象可以用C++定义并提供一个virtual
回调成员函数。然后可以继承此对象并覆盖 Python 中的回调函数。然后可以将继承的对象传递给 C++ 函数而不是回调函数。为此,您需要为此类回调启用导演功能 class.
不过,这确实需要更改底层 C++ 库。
在我看来,ctypes
and a 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*) {
= (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+ 兼容性的建议。
我正在尝试使用 SWIG 为 python 包装一个 C++ 库。该库经常使用回调函数,通过将 某种类型的回调函数 传递给 class 方法。
现在,包装代码后,我想从 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 *'"
有什么方法可以 "cast" 将 python 函数转换为 SWIG 包装器接受的类型?
您可以使用"directors"在Python中实现回调逻辑。
基本上,不是传递回调函数,而是传递回调对象。基础对象可以用C++定义并提供一个virtual
回调成员函数。然后可以继承此对象并覆盖 Python 中的回调函数。然后可以将继承的对象传递给 C++ 函数而不是回调函数。为此,您需要为此类回调启用导演功能 class.
不过,这确实需要更改底层 C++ 库。
在我看来,ctypes
and a 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*) {
= (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+ 兼容性的建议。