Add SWIG post.
authorW. Trevor King <wking@drexel.edu>
Sat, 10 Mar 2012 12:09:49 +0000 (07:09 -0500)
committerW. Trevor King <wking@drexel.edu>
Sat, 10 Mar 2012 12:09:49 +0000 (07:09 -0500)
.gitignore
posts/SWIG.mdwn [new file with mode: 0644]
posts/SWIG/Makefile [new file with mode: 0644]
posts/SWIG/example.c [new file with mode: 0644]
posts/SWIG/example.h [new file with mode: 0644]
posts/SWIG/example.i [new file with mode: 0644]
posts/SWIG/setup.py [new file with mode: 0644]
posts/SWIG/test.py [new file with mode: 0755]
posts/pycomedi.mdwn

index c227f0576c46536e990e4a29c929c3393b233893..4fd778d5503360257ae22ae7a660c3896a0ce837 100644 (file)
@@ -1,4 +1,5 @@
 *.pyc
+*.gz
 /.ikiwiki
 /html
 /recentchanges
diff --git a/posts/SWIG.mdwn b/posts/SWIG.mdwn
new file mode 100644 (file)
index 0000000..0eb1e83
--- /dev/null
@@ -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
+    <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][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.
+
+<!-- test these doctests from the SWIG directory with
+  LD_LIBRARY_PATH=. nosetests --with-doctest --doctest-extension=mdwn ../SWIG.mdwn
+  -->
+
+[[!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 (file)
index 0000000..6e1b2ee
--- /dev/null
@@ -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 (file)
index 0000000..e856b15
--- /dev/null
@@ -0,0 +1,41 @@
+#include <stdio.h>
+#include "example.h"
+
+void print_pointer(size_t n, double *x)
+{
+       size_t i;
+       for (i=0; i<n; i++) {
+               printf("%d: %g\n", (int)i, x[i]);
+       }
+       return;
+}
+
+void print_array(size_t n, double x[])
+{
+       print_pointer(n, x);
+       return;
+}
+
+void print_vector(double_vector_t *v)
+{
+       print_pointer(v->n, 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 (file)
index 0000000..8c1f294
--- /dev/null
@@ -0,0 +1,20 @@
+#include <stddef.h>  /* 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 (file)
index 0000000..50b2b49
--- /dev/null
@@ -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 (file)
index 0000000..00709c6
--- /dev/null
@@ -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 (executable)
index 0000000..c8621a3
--- /dev/null
@@ -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])
index 30e19b13f01aa8b0ab5f245b22575b15b312a962..3391b4c7142785ef9207f487428e88c27b2e3812 100644 (file)
@@ -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/