Skip to content

Instantly share code, notes, and snippets.

@mattpitkin
Last active March 15, 2023 10:55
Show Gist options
  • Save mattpitkin/b24794dec094505a209a80928fd57166 to your computer and use it in GitHub Desktop.
Save mattpitkin/b24794dec094505a209a80928fd57166 to your computer and use it in GitHub Desktop.
Adding python functionality to a SWIG wrapped structure

Adding python functionality to a SWIG-wrapped structure

This is a little example of adding some python-object-like functionality to a C structure that is SWIG-wrapped for use in python. What I've done mainly comes from this StackOverflow question, and the very useful answer, but is written here to remind me.

Say you have some C code containing a structure like this:

/* testswig.h file */
#include <stdlib.h>
#include <stdio.h>

typedef struct tagteststruct{
  double *data;
  size_t len;
} teststruct;

teststruct *CreateStruct(size_t len);

where the structure will contain a data array of length len. The function CreateStruct() allocates memory for an instantiation of the structure, and is defined as

/* testswig.c file */
#include "testswig.h"

/* function for allocating memory for test struct */
teststruct *CreateStruct(size_t len){
  teststruct *ts = NULL;

  ts = (teststruct *)malloc(sizeof(teststruct));
  ts->data = (double *)malloc(sizeof(double)*len);
  ts->len = len;
  return ts;
}

If you wrap this with SWIG for use in python, then it might be useful to have some python list-like methods available to, e.g., add or get items from the data array. To do this you can create the following SWIG interface file:

/* testswig.i */

%module testswig

%include "exception.i"

%{
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "testswig.h"
static int teststructErr = 0; // flag to save test struct error state
%}

%include "testswig.h"

// set exception handling for __getitem__
%exception tagteststruct::__getitem__ {
  assert(!teststructErr);
  $action
  if ( teststructErr ){
    teststructErr = 0; // clear flag for next time
    SWIG_exception(SWIG_IndexError, "Index out of bounds");
  }
}

// set exception handling for __setitem__
%exception tagteststruct::__setitem__ {
  assert(!teststructErr);
  $action
  if ( teststructErr ){
    teststructErr = 0; // clear flag for next time
    SWIG_exception(SWIG_IndexError, "Index out of bounds");
  }
}

// set exception handling for insert()
%exception tagteststruct::insert {
  assert(!teststructErr);
  $action
  if ( teststructErr ){
    teststructErr = 0; // clear flag for next time
    SWIG_exception(SWIG_IndexError, "Index out of bounds");
  }
}

// "extend" the structure with various methods
%extend tagteststruct{
  // add a __getitem__ method to the structure to get values from the data array
  double __getitem__(size_t i) {
    if (i >= $self->len) {
      teststructErr = 1;
      return 0;
    }
    return $self->data[i];
  }

  // add a __setitem__ method to the structure to set values in the data array
  void __setitem__(size_t i, double value) {
    if ( i >= $self->len ){
      teststructErr = 1;
      return;
    }
    $self->data[i] = value;  
  }

  
  size_t __len__(){
    return $self->len;
  }

  void insert(size_t i, double value) {
    if ( i >= $self->len ){
      teststructErr = 1;
      return;
    }
    $self->data[i] = value;
  }
}

In the above example, it adds the following methods to the structure:

  • __getitem__: this allows the structure's data array to be accessed like a list item in python, e.g., using x[0] returns the value in teststruct->data[0]
  • __setitem__: this allows the structure's data array values to be set like a list item in python, e.g., using x[0] = 1.2 sets the value in teststruct->data[0]
  • __len__: this returns the length of the data array when using len(x)
  • insert(): this inserts a value into a particular index in the array like with __setitem__

The example also shows how to perform some exception checking for these methods, in particular, making sure the requested index does not exceed the size of the array.

To compile and use this example, you could do the following (see, e.g., SWIG's tutorial):

$ swig -python testswig.i
$ gcc -c testswig.c testswig_wrap.c -fPIC -I/usr/include/python2.7
$ ld -shared testswig.o testswig_wrap.o -o _testswig.so

where, in this case, the -I/usr/include/python2.7 flag points to the path containing the Python.h file. The testswig_wrap.c file is generated by the swig command.

The structure can then be used in python as in the following example:

>>> from testswig import CreateStruct
>>> # create an instance of the structure with 10 elements
>>> x = CreateStruct(10)
>>> # set the 5th element of the data array to 1.3
>>> x[4] = 1.3
>>> # output the 5th element of the array
>>> print(x[4])
1.3
>>> # output the length of the array
>>> print(len(x))
10
@xiaoniaoyou
Copy link

Useful!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment