Exposing an array interface with SWIG for a C/C++ structure

Sometimes, a C or C++ array structure must be used in Python, and it’s always better to be able to use the underlying array to do some Numpy computations. To that purpose, Numpy proposes the array interface.

I will now expose an efficient way to use SWIG to generate the array interface and exposing the __array_struct__ property.

Let’s start with a simple C structure (what is really relevant is that the structure encompasses an arrau). If you don’t have the strides and the shape, you will have to add them to the structure (create a new structure with the structure to wrap inside, for example):

#ifndef NUMPY_SWIG
#define NUMPY_SWIG
typedef struct _SignedIntBuf
  int* data;
  int shape[2];
  int strides[2];
} SignedIntBuf;

Now, we can create the SWIG wrapper file (it will be split in several part for the explanation).

%include "numpy.i"
%init %{
%module (docstring="This is a Python SWIG-wrapped module") numpy_swig

A module will now be created with an associated docstring with the argument docstring.

#include "numpy_swig.h"
void delete_SignedIntBuf(SignedIntBuf* buffer)
void free_array_interface( void* ptr, void *arr )
  PyArrayInterface* inter;
  PyObject* arrpy;
  inter = (PyArrayInterface*)ptr;
  arrpy = (PyObject*)arr;

We need two destruction functions. The first is the actual structure destructor, and the second one is the Python deallocation function.

%inline %{PyObject* get__array_struct__(PyObject* self, int* shape, int* strides, int*data)
  PyArrayInterface* inter;
  PyObject* obj;
  int nd;
  nd = 2;
  inter = (PyArrayInterface*)malloc(sizeof(PyArrayInterface));
  if (inter==NULL)
    return PyErr_NoMemory();
  inter->two = 2;
  inter->nd = nd;
  inter->typekind = 'i';
  inter->itemsize = sizeof(int);
  inter->strides = strides;
  inter->shape = shape;
  inter->data = data;
  obj = PyCObject_FromVoidPtrAndDesc((void*)inter, (void*)self, free_array_interface);
  return obj;

This is the main function which will create the Numpy array interface from the structure. Its parameters are the to-be-wrapped object as well as every other argument like the shape, the stride or the actual data. Once an interface is created, it is populated with appropriate values (like the data type) and then the function PyCObject_FromVoidPtrAndDesc() will be called to create the sought Python object.

Now I extend with SWIG the C structure:

%include numpy_swig.h
%extend SignedIntBuf{
  %feature("autodoc", "The comment docstring")SignedIntBuf;
  SignedIntBuf(int width, int height)
    SignedIntBuf* buffer = (SignedIntBuf*) malloc(sizeof(SignedIntBuf));
    buffer->shape[0] = height;
    buffer->shape[1] = width;
    buffer->strides[0] = width * sizeof(int);
    buffer->strides[1] = sizeof(int);
    buffer->data = (int*) malloc(width*height*sizeof(int));
    return buffer;
   char *__str__()
     static char tmp[1024];
     int i, j;
     int used = 0;
     used += sprintf(tmp, "Array:\n");
     for(i=0; i < $self->shape[0]; i++)
       for(j=0; j < $self->shape[1]; j++)
         used += sprintf(tmp + used, "%d\t", $self->data[j + i*$self->shape[1]]);
       used += sprintf(tmp + used, "\n");
     return tmp;
    def __array_struct__get(self):
      return get__array_struct__(self, self.shape, self.strides, self.data)
    __array_struct__ = property(__array_struct__get, doc='Array protocol')

After the inclusion of the C header, the structure is extended, the constructor is written there completely, the destructor will be the function delete_SignedIntBuf().

Some notes:

  • %feature(“autodoc”, value); automatically adds a docstring if value equals 1. Here, I’ve put an explicit comment for the structure.
  • I’ve added a sample for the __str__() function which returns a static character array. You could return a std::string for more robustness.

The last feature is %pythoncode. It indicates to SWIG that it should copy and paste the code directly in the Python wrapper. I had to do this because __array_struct__ must be a Python property. Furthermore, the property getter can’t be the SWIG function. This is because SWIG does not wrap objects directly, but uses classic Python functions, and as a consequence, the first argument of the C function can’t be used.

The underlying C function must have access to the Python wrapper object to correctly count the reference on the object. To do, the object must be passed as a usual argument, which is what the intermediate method __array_struct__get() does.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.