Python Swig interface for C function allocating a list of structures

Question:

I’m trying to get the following C function to be exposed as a python interface.

void myfunc(struct MyStruct** list, int* size) {
    int n = 10;
    *size = n;
    *list = (struct MyStruct*) malloc(n * sizeof(struct MyStruct));
    for (int i = 0; i < n; i++) {
        (*list)[i].x = i;
        (*list)[i].y = i * 0.1;
    }
}

Unfortunately the swig documentation didn’t help narrow down on a solution. Could you please help with any pointers or code references to how we can write a corresponding swig interface file to make this function be called from python?

It would be a bonus if I could access this as a list of objects in python.

Asked By: Maverickgugu

||

Answers:

One way to do it (error checking left out):

SWIG Interface File – test.i

%module test

%{ // code to include directly in the wrapper
#include <stdlib.h>
#include <stdio.h>

struct MyStruct {
    int x;
    double y;
};

void myfunc(struct MyStruct** list, int* size) {
    int n = 10;
    *size = n;
    *list = (struct MyStruct*) malloc(n * sizeof(struct MyStruct));
    for (int i = 0; i < n; i++) {
        (*list)[i].x = i;
        (*list)[i].y = i * 0.1;
    }
}
%}

// On input, do not require the list/size parameters.
// Instead, declare tmp variables and pass them by reference
// to capture the output arguments.
%typemap(in, numinputs=0) (struct MyStruct** list, int* size) (struct MyStruct* tmp, int size) %{
    $1 = &tmp;
    $2 = &size;
%}

// After the function call, append the returned pointer and size to the output result.
%typemap(argout) (struct MyStruct** list, int* size) %{
    PyObject* obj = SWIG_NewPointerObj(*$1, $descriptor(struct MyStruct*), 0);
    PyObject* s = PyLong_FromLong(*$2);
    $result = SWIG_Python_AppendOutput($result, obj);
    $result = SWIG_Python_AppendOutput($result, s);
%}

// Use the pre-defined SWIG typemaps to handle C pointers and arrays.
%include <carrays.i>
%include <cpointer.i>
%pointer_functions(struct MyStruct, pMyStruct);
%array_functions(struct MyStruct, MyStructArray);

// Make Python wrappers for the below struct and function.
struct MyStruct {
    int x;
    double y;
};

void myfunc(struct MyStruct** list, int* size);

Python test file – example.py

import test

# Helper function to put all the returned data in a list
# and manage the allocated pointer.
def myfunc():
    ptr, size = test.myfunc()
    try:
        arr = []
        objs = [test.MyStructArray_getitem(ptr, i) for i in range(size)]
        return [(obj.x, obj.y) for obj in objs]
    finally:
        test.delete_pMyStruct(ptr)

print(myfunc())

Output:

[(0, 0.0), (1, 0.1), (2, 0.2), (3, 0.30000000000000004), (4, 0.4), (5, 0.5), (6, 0.6000000000000001), (7, 0.7000000000000001), (8, 0.8), (9, 0.9)]

If you want to completely handle the data conversion in the SWIG wrapper, here’s an example:

test.i

%module test

%{ // code to include directly in the wrapper
#include <stdlib.h>
#include <stdio.h>

struct MyStruct {
    int x;
    double y;
};

void myfunc(struct MyStruct** list, int* size) {
    int n = 10;
    *size = n;
    *list = (struct MyStruct*) malloc(n * sizeof(struct MyStruct));
    for (int i = 0; i < n; i++) {
        (*list)[i].x = i;
        (*list)[i].y = i * 0.1;
    }
}
%}

// On input, do not require the list/size parameters.
// Instead, declare tmp variables and pass them by reference
// to capture the output arguments.
%typemap(in, numinputs=0) (struct MyStruct** list, int* size) (struct MyStruct* tmp, int size) %{
    $1 = &tmp;
    $2 = &size;
%}

// After the function call, Build a list of tuples with the x/y data from the structs.
%typemap(argout) (struct MyStruct** list, int* size) (PyObject* list) %{
    list = PyList_New(*$2);
    for(int i = 0; i < *$2; ++i) {
        PyObject* t = PyTuple_New(2);
        PyTuple_SET_ITEM(t, 0, PyLong_FromLong((*$1)[i].x));
        PyTuple_SET_ITEM(t, 1, PyFloat_FromDouble((*$1)[i].y));
        PyList_SET_ITEM(list, i, t);
    }
    $result = SWIG_Python_AppendOutput($result, list);
%}

// Free the allocated structure array
%typemap(freearg) (struct MyStruct** list, int* size) %{
    free(*$1);
%}

// Make Python wrappers for the below struct and function.
struct MyStruct {
    int x;
    double y;
};

void myfunc(struct MyStruct** list, int* size);

example.py

import test
print(test.myfunc())

Output:

[(0, 0.0), (1, 0.1), (2, 0.2), (3, 0.30000000000000004), (4, 0.4), (5, 0.5), (6, 0.6000000000000001), (7, 0.7000000000000001), (8, 0.8), (9, 0.9)]
Answered By: Mark Tolonen

Building on top Mark’s second approach. I came up with the following interface file. I’m not sure if there would be leaks, just adding it here to seek comments.

%module example
%{
#include "example.h"
%}

// On input, do not require the list/size parameters.
// Instead, declare tmp variables and pass them by reference
// to capture the output arguments.
%typemap(in, numinputs=0) (struct MyStruct** list, int* size) (struct MyStruct* tmp, int size) %{
    $1 = &tmp;
    $2 = &size;
%}

// After the function call, Build a list of objects from the structs.
%typemap(argout) (struct MyStruct** list, int* size) (PyObject* list) %{
    list = PyList_New(*$2);
    for(int i = 0; i < *$2; ++i) {
        struct MyStruct* t = (struct MyStruct*)malloc(sizeof((*$1)[i]));
        t->x = (*$1)[i].x;
        t->y = (*$1)[i].y;
        PyObject* t1 = SWIG_NewPointerObj(t, SWIGTYPE_p_MyStruct, SWIG_POINTER_OWN);
        PyList_SET_ITEM(list, i, (PyObject*)t1);
    }
    $result = SWIG_Python_AppendOutput($result, list);
%}

// Free the allocated structure array
%typemap(freearg) (struct MyStruct** list, int* size) %{
    free(*$1);
%}

%include "example.h"

Now the objects could be accessed more organically? Unsure about the memory handling part in this case.

>>> import example as e
>>> a = e.myfunc()
0xbf2b30 
>>> al = [(b.x,b.y) for b in a]
>>> al
[(0, 0.0), (1, 0.10000000149011612), (2, 0.20000000298023224), (3, 0.30000001192092896), (4, 0.4000000059604645), (5, 0.5), (6, 0.6000000238418579), (7, 0.699999988079071), (8, 0.800000011920929), (9, 0.8999999761581421)]
Answered By: Maverickgugu
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.