Passing python functions to SWIG wrapped C++ code

Posted on

Question :

Passing python functions to SWIG wrapped C++ code

I am trying to wrap a C++ library for python, using SWIG. The library uses callback functions frequently, by passing callback functions of certain type to class methods.

Now, after wrapping the code, I would like to create the callback logic from python. Is this possible? Here is an experiment I was doing to find it out .. does not work at the moment.

The header and swig files are as follows:

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 ..

Finally, I test in 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!

The last line throws me “TypeError: in method ‘handleri_eater’, argument 1 of type ‘handleri *'”

Is there any way to “cast” the python function to a type accepted by the SWIG wrapper?

Answer #1:

You can implement the callback logic in Python by using “directors”.

Basically, instead of passing callback functions, you pass callback objects instead. The base object can be defined in C++ and provide a virtual callback member function. This object can then be inherited from and the callback function overwritten in Python. The inherited object can then be passed to a C++ function instead of a callback function. For this to work, you need to enable the director feature for such a callback class.

This does require changing the underlying C++ library, though.

Answered By: m7thon

Answer #2:

Seems to me that a combination of ctypes and a SWIG typemap would be the easiest way to solve the problem. ctypes makes it easy to generate a C function that calls a Python callable. The Python code should be along the lines of:

import example

# python callback
def py_callback(i, s):
    print( 'py_callback(%d, %s)'%(i, s) )

example.use_callback(py_callback)

On the SWIG side we have: (1) a Python function use_callback that wraps the Python callback with a ctypes wrapper, and passes the address the wrapper as an integer to _example.use_callback(), and (2) a SWIG typemap that extracts the address and casts itto the appropriate function pointer.

%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)

%}

You can find this complete example with a CMakeLists.txt file here.

edit: incorporated @Flexo suggestion to move the Python part into the %pythoncode block of the SWIG file.

edit: incorporated @user87746 suggestion for Python 3.6+ compatibility.

Answered By: sterin

Leave a Reply

Your email address will not be published.