Home > Software design >  Python Swig interface for C function allocating a list of structures
Python Swig interface for C function allocating a list of structures

Time:01-28

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.

CodePudding user response:

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)]
  • Related