From: W. Trevor King Date: Sat, 10 Mar 2012 12:09:49 +0000 (-0500) Subject: Add SWIG post. X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=7b99c8ba59bcd96f2fb1170ca0e29dab30cfa250;p=blog.git Add SWIG post. --- diff --git a/.gitignore b/.gitignore index c227f05..4fd778d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.pyc +*.gz /.ikiwiki /html /recentchanges diff --git a/posts/SWIG.mdwn b/posts/SWIG.mdwn new file mode 100644 index 0000000..0eb1e83 --- /dev/null +++ b/posts/SWIG.mdwn @@ -0,0 +1,137 @@ +[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++|Cpp]] 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][comedilib-patch]. + + + >>> 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|array_class-example.tar.gz]]. + +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 + > + +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 + + >>> 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 + + +So `.cast()` gets you from `proxy of ` to ``. 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][classmethod]: + + >>> 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][ownership]: + + >>> 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. + + + +[[!tag tags/tools]] +[[!tag tags/C]] +[[!tag tags/Python]] + +[SWIG]: http://www.swig.org/ +[`carrays.i`]: http://www.swig.org/Doc2.0/Library.html#Library_carrays +[docs]: http://www.swig.org/Doc2.0/Library.html#Library_carrays +[comedilib-patch]: http://comedi.org/git?p=comedi/comedilib.git;a=blobdiff;f=swig/comedi.i;h=5da6160d91d206d007e20c9ac5091d1735afdd30;hp=581997542927fd31cd2e0d03c220377774cfa600;hb=3fe8e6baac051d80906c6fac6c18c04c8df9ce4a;hpb=880074831499ba68c17a1c2653d71d6eef3b9cfb +[classmethod]: http://docs.python.org/library/functions.html#classmethod +[ownership]: http://www.swig.org/Doc2.0/Python.html#Python_nn30 diff --git a/posts/SWIG/Makefile b/posts/SWIG/Makefile new file mode 100644 index 0000000..6e1b2ee --- /dev/null +++ b/posts/SWIG/Makefile @@ -0,0 +1,27 @@ +SOURCE = example.c example.h example.i test.py setup.py Makefile + +.PHONY: test clean + +test: test.py _example.so test.py + LD_LIBRARY_PATH=. ./$< + +clean: + rm -rf *_wrap.c *.o *.so example.py* build + +array_class-example.tar.gz: $(SOURCE) + mkdir example + cp -r $^ example/ + tar -czf $@ example/ + rm -rf example/ + +_example.so: example.py + cp $$(find build -name $@) . + +example.py: libexample.so example.i + python setup.py build || rm -rf build + +lib%.so: %.o + $(CC) -shared -Wl,-soname,$@ -o $@ $< + +%.o : %.c %.h + $(CC) $(CFLAGS) -fPIC -c $< diff --git a/posts/SWIG/example.c b/posts/SWIG/example.c new file mode 100644 index 0000000..e856b15 --- /dev/null +++ b/posts/SWIG/example.c @@ -0,0 +1,41 @@ +#include +#include "example.h" + +void print_pointer(size_t n, double *x) +{ + size_t i; + for (i=0; in, v->data); + return; +} + +void print_sample_pointer(size_t n, sample_t *x) +{ + print_pointer(n, x); + return; +} + +void print_sample_array(size_t n, sample_t x[]) +{ + print_pointer(n, x); + return; +} + +void print_sample_vector(sample_vector_t *v) +{ + print_pointer(v->n, v->data); + return; +} diff --git a/posts/SWIG/example.h b/posts/SWIG/example.h new file mode 100644 index 0000000..8c1f294 --- /dev/null +++ b/posts/SWIG/example.h @@ -0,0 +1,20 @@ +#include /* size_t */ + +typedef double sample_t; + +typedef struct double_vector_struct { + size_t n; + double *data; +} double_vector_t; + +typedef struct sample_vector_struct { + size_t n; + sample_t *data; +} sample_vector_t; + +void print_pointer(size_t n, double *x); +void print_array(size_t n, double x[]); +void print_vector(double_vector_t *v); +void print_sample_pointer(size_t n, sample_t *x); +void print_sample_array(size_t n, sample_t x[]); +void print_sample_vector(sample_vector_t *v); diff --git a/posts/SWIG/example.i b/posts/SWIG/example.i new file mode 100644 index 0000000..50b2b49 --- /dev/null +++ b/posts/SWIG/example.i @@ -0,0 +1,8 @@ +%module example +%include "carrays.i" +%{ +#include "example.h" +%} +%include "example.h" +%array_class(double, double_array); +%array_class(sample_t, sample_array); diff --git a/posts/SWIG/setup.py b/posts/SWIG/setup.py new file mode 100644 index 0000000..00709c6 --- /dev/null +++ b/posts/SWIG/setup.py @@ -0,0 +1,16 @@ +from distutils.core import setup, Extension + +module1 = Extension( + '_example', + library_dirs = ['.'], + #swig_opts = ['-I.'], + libraries = ['example'], + sources = ['example.i']) + +setup( + name='example', + version = '0.1', + description = 'Python wrapper my array_class example module', + ext_modules = [module1], + py_modules = ['example'], + ) diff --git a/posts/SWIG/test.py b/posts/SWIG/test.py new file mode 100755 index 0000000..c8621a3 --- /dev/null +++ b/posts/SWIG/test.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python + +import example + +n = 3 + +print('double allocation and initialization in Python') +data = example.double_array(n) +for i in range(0,n): + data[i] = i**2 + +print('passing doubles to C') +example.print_array(n, data) +example.print_pointer(n, data) +vector = example.double_vector_t() +vector.n = n +vector.data = data +example.print_vector(vector) + +print('accessing doubles from Python') +print(data[n-1]) +try: + print(vector.data[n-1]) +except TypeError, e: + # 'SwigPyObject' object is not subscriptable + print e +print(example.double_array.frompointer(vector.data)[n-1]) + +print('sample_t allocation and initialization in Python') +data = example.sample_array(n) +for i in range(0,n): + data[i] = 2*i + +print('passing sample_ts to C') +try: + example.print_sample_pointer(n, data) +except TypeError, e: + # in method 'print_sample_pointer', argument 2 of type 'sample_t *' + print(e) +try: + example.print_sample_array(n, data) +except TypeError, e: + # in method 'print_sample_array', argument 2 of type 'sample_t []' + print e + +# the .cast() method lets you pass sample_t data to the funtion +example.print_sample_pointer(n, data.cast()) +example.print_sample_array(n, data.cast()) + +vector = example.sample_vector_t() +vector.n = n +try: + vector.data = data +except TypeError, e: + # in method 'sample_vector_t_data_set', argument 2 of type 'sample_t *' + print e +vector.data = data.cast() +example.print_sample_vector(vector) + +print('accessing sample_ts from Python') +print(data[n-1]) +try: + print(vector.data[n-1]) +except TypeError, e: + # 'SwigPyObject' object is not subscriptable + print e +print(example.sample_array.frompointer(vector.data)[n-1]) diff --git a/posts/pycomedi.mdwn b/posts/pycomedi.mdwn index 30e19b1..3391b4c 100644 --- a/posts/pycomedi.mdwn +++ b/posts/pycomedi.mdwn @@ -1,13 +1,12 @@ [[!meta title="pycomedi"]] [[!template id=gitrepo repo=pycomedi]] -I was getting frustrated with [Comedi][]'s [SWIG][] wrappers, so I +I was getting frustrated with [Comedi][]'s [[SWIG]] wrappers, so I wrote a more object-oriented wrapper using [Cython][]. The `README` is posted on the [PyPI page][pypi]. [Comedi]: http://www.comedi.org/ -[SWIG]: http://www.swig.org/ [Cython]: http://cython.org/ [pypi]: http://pypi.python.org/pypi/pycomedi/