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

将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

我试图用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 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)一个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文件找到这个完整的示例

编辑:合并@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