SWIG is a Simplified Wrapper and Interface Generator. It makes it very easy to provide a quick-and-dirty wrapper so you can call code written in C or C++ from code written in another (e.g. Python). I don't do much with SWIG, because while building an object oriented wrapper in SWIG is possible, I could never get it to feel natural (I like Cython better). Here are my notes from when I do have to interact with SWIG.
%array_class
and memory management
%array_class
(defined in carrays.i) lets you wrap a C array in
a class-based interface. The example from the docs is nice and
concise, but I was running into problems.
>>> import example
>>> n = 3
>>> data = example.sample_array(n)
>>> for i in range(n):
... data[i] = 2*i + 3
>>> example.print_sample_pointer(n, data)
Traceback (most recent call last):
...
TypeError: in method 'print_sample_pointer', argument 2 of type 'sample_t *'
I just bumped into these errors again while trying to add an
insn_array
class to Comedi's wrapper:
%array_class(comedi_insn, insn_array);
so I decided it was time to buckle down and figure out what was going on. All of the non-Comedi examples here are based on my example test code.
The basic problem is that while you and I realize that an
array_class
-based instance is interchangable with the underlying
pointer, SWIG does not. For example, I've defined a sample_vector_t
struct
:
typedef double sample_t;
typedef struct sample_vector_struct {
size_t n;
sample_t *data;
} sample_vector_t;
and a sample_array
class:
%array_class(sample_t, sample_array);
A bare instance of the double array class has fancy SWIG additions for getting and setting attributes. The class that adds the extra goodies is SWIG's proxy class:
>>> print(data) # doctest: +ELLIPSIS
<example.sample_array; proxy of <Swig Object of type 'sample_array *' at 0x...> >
However, C functions and structs interact with the bare pointer
(i.e. without the proxy goodies). You can use the .cast()
method to
remove the goodies:
>>> data.cast() # doctest: +ELLIPSIS
<Swig Object of type 'double *' at 0x...>
>>> example.print_sample_pointer(n, data.cast())
>>> vector = example.sample_vector_t()
>>> vector.n = n
>>> vector.data = data
Traceback (most recent call last):
...
TypeError: in method 'sample_vector_t_data_set', argument 2 of type 'sample_t *'
>>> vector.data = data.cast()
>>> vector.data # doctest: +ELLIPSIS
<Swig Object of type 'double *' at 0x...>
So .cast()
gets you from proxy of <Swig Object ...>
to <Swig
Object ...>
. How you go the other way? You'll need this if you want
to do something extra fancy, like accessing the array members ;).
>>> vector.data[0]
Traceback (most recent call last):
...
TypeError: 'SwigPyObject' object is not subscriptable
The answer here is the .frompointer()
method, which can function as
a class method:
>>> reconst_data = example.sample_array.frompointer(vector.data)
>>> reconst_data[n-1]
7.0
Or as a single line:
>>> example.sample_array.frompointer(vector.data)[n-1]
7.0
I chose the somewhat awkward name of reconst_data
for the
reconstitued data, because if you use data
, you clobber the earlier
example.sample_array(n)
definition. After the clobber, Python
garbage collects the old data
, and becase the old data claims it
owns the underlying memory, Python frees the memory. This leaves
vector.data
and reconst_data
pointing to unallocated memory, which
is probably not what you want. If keeping references to the original
objects (like I did above with data
) is too annoying, you have to
manually tweak the ownership flag:
>>> data.thisown
True
>>> data.thisown = False
>>> data = example.sample_array.frompointer(vector.data)
>>> data[n-1]
7.0
This way, when data
is clobbered, SWIG doesn't release the
underlying array (because data
no longer claims to own the array).
However, vector
doesn't own the array either, so you'll have to
remember to reattach the array to somthing that will clean it up
before vector goes out of scope to avoid leaking memory:
>>> data.thisown = True
>>> del vector, data
For deeply nested structures, this can be annoying, but it will work.