Search code examples
pythoncpython-3.xlistswig

Python Swig interface for C function allocating a list of structures


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.


Solution

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