将python函数传递给SWIG包装的C++;代码
我试图用SWIG包装Python的C++库。该库通过将特定类型的回调函数传递给类方法,经常使用回调函数 现在,在包装代码之后,我想从python创建回调逻辑。这可能吗?这是我为找到答案而做的一个实验。。现在不行 头文件和swig文件如下所示: paska.h:将python函数传递给SWIG包装的C++;代码,python,c++,swig,Python,C++,Swig,我试图用SWIG包装Python的C++库。该库通过将特定类型的回调函数传递给类方法,经常使用回调函数 现在,在包装代码之后,我想从python创建回调逻辑。这可能吗?这是我为找到答案而做的一个实验。。现在不行 头文件和swig文件如下所示: paska.h: typedef void (handleri)(int code, char* codename); // handleri is now an alias to a function that eats int, string and
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);
帕斯卡一号:
%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:在方法'handleri_eater'中,参数1的类型为'handleri*'”
有没有办法将python函数“强制转换”为SWIG包装器接受的类型?您可以通过使用在python中实现回调逻辑 基本上,不是传递回调函数,而是传递回调对象。可以在C++中定义基本对象并提供<代码>虚拟< /COD>回调成员函数。然后可以从中继承该对象,并在Python中覆盖回调函数。继承的对象可以传递给C++函数而不是回调函数。要使其工作,需要为此类回调类启用director功能
<>这确实需要改变底层C++库。 在我看来,组合和SWIG将是解决问题的最简单的方法。code>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\u callback
,它用ctypes
包装器包装Python回调,并将包装器中的地址作为整数传递给。(2)一个SWIGtypemap
,它提取地址并将其强制转换为适当的函数指针
%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文件找到这个完整的示例
编辑:合并@flex建议,将Python部分移动到SWIG文件的%pythoncode块中
编辑:合并了@user87746对Python 3.6+兼容性的建议。唉,据说“SWIG完全支持函数指针,前提是回调函数是用C定义的,而不是用目标语言定义的。”。。所以我想这是不可能的。任何想法如何从Python做回调逻辑……当然不是不可能的,我们可以定制一切,但是既然你使用C++而不是C,为什么不使用<代码> STD::函数< /代码>?这使得写一个更清晰的答案更简单——如果你对这个改变感到满意,我会为你写一个完整的解决方案。(本质上是的Python版本,或者是的泛化版本)如果做不到这一点,那么真正的回调会得到一个void*
参数,您可以在注册它们时设置它吗?这让生活变得更干净。我遇到了同样的问题,你能展示你的最终解决方案吗?在我的最终解决方案中,我决定不将python函数传递到cpp级别,而是在cpp级别执行所有操作。:)我喜欢使用这样的ctypes。我认为可以通过将所有ctypes位隐藏在%pythoncode%{…%}
块中,使SWIG/Python位稍微整洁一点,这样从Pyhton模块的用户的角度来看,他们只需调用use\u callback
,Python可调用,其他一切都在幕后发生。@flex,好建议,我已经把它合并到这个例子中了。接下来的一个问题是:“f=py\u回调类型(py\u回调)”会发生什么。。python代码是否转换为机器代码或类似的东西。。?这东西在引擎盖下是如何工作的?模块使用ctypes
来实现这一点。从Wikipedia页面:libffi可以生成一个指向函数的指针,该函数可以接受和解码运行时定义的任何参数组合。请注意,对于python 3.6,您需要使用typecast:PyLong\u AsVoidPtr
而不是PyInt\u AsLong
谢谢您通知我有关控制器的情况。。到现在为止,我发现它们非常有用。。但我仍然一点也不知道它们是如何工作的。。!python代码是否在某个阶段“编译”并传递给C,或者swig是否将python“运行时环境”嵌入到包装好的C代码中(即是否从C执行“PyRun_SimpleString”等)。我很困惑。我也只知道SWIG手册上说的那么多。我确信从来没有编译过python代码。但是python代码显然是从C中调用的。这需要嵌入python运行时环境吗?嗯。。我猜它在包装器中调用了python C api的PyObject_CallMethod