mkogg.py: Fix 'self.get_mp4_metadata(self, source)'
[blog.git] / posts / SWIG.mdwn
1 [SWIG][] is a Simplified Wrapper and Interface Generator.  It makes it
2 very easy to provide a quick-and-dirty wrapper so you can call code
3 written in [[C]] or [[C++|Cpp]] from code written in another
4 (e.g. [[Python]]).  I don't do much with SWIG, because while building
5 an object oriented wrapper in SWIG is possible, I could never get it
6 to feel natural (I like [[Cython]] better).  Here are my notes from
7 when I *do* have to interact with SWIG.
8
9 `%array_class` and memory management
10 ====================================
11
12 `%array_class` (defined in [carrays.i][]) lets you wrap a C array in
13 a class-based interface.  The example from the [docs][] is nice and
14 concise, but I was [running into problems][comedilib-patch].
15
16
17     >>> import example
18     >>> n = 3
19     >>> data = example.sample_array(n)
20     >>> for i in range(n):
21     ...     data[i] = 2*i + 3
22     >>> example.print_sample_pointer(n, data)
23     Traceback (most recent call last):
24       ...
25     TypeError: in method 'print_sample_pointer', argument 2 of type 'sample_t *'
26
27 I just bumped into these errors again while trying to add an
28 `insn_array` class to [[Comedi]]'s wrapper:
29
30     %array_class(comedi_insn, insn_array);    
31
32 so I decided it was time to buckle down and figure out what was going
33 on.  All of the non-Comedi examples here are based on my [[example
34 test code|array_class-example.tar.gz]].
35
36 The basic problem is that while you and I realize that an
37 `array_class`-based instance is interchangable with the underlying
38 pointer, SWIG does not.  For example, I've defined a `sample_vector_t`
39 `struct`:
40
41     typedef double sample_t;
42     typedef struct sample_vector_struct {
43       size_t n;
44       sample_t *data;
45     } sample_vector_t;
46
47 and a `sample_array` class:
48
49     %array_class(sample_t, sample_array);
50
51 A bare instance of the double array class has fancy SWIG additions for
52 getting and setting attributes.  The class that adds the extra goodies
53 is SWIG's *proxy class*:
54
55     >>> print(data)  # doctest: +ELLIPSIS
56     <example.sample_array; proxy of <Swig Object of type 'sample_array *' at 0x...> >
57
58 However, C functions and structs interact with the bare pointer
59 (i.e. without the proxy goodies).  You can use the `.cast()` method to
60 remove the goodies:
61
62     >>> data.cast()  # doctest: +ELLIPSIS
63     <Swig Object of type 'double *' at 0x...>
64     >>> example.print_sample_pointer(n, data.cast())
65     >>> vector = example.sample_vector_t()
66     >>> vector.n = n
67     >>> vector.data = data
68     Traceback (most recent call last):
69       ...
70     TypeError: in method 'sample_vector_t_data_set', argument 2 of type 'sample_t *'
71     >>> vector.data = data.cast()
72     >>> vector.data  # doctest: +ELLIPSIS
73     <Swig Object of type 'double *' at 0x...>
74
75 So `.cast()` gets you from `proxy of <Swig Object ...>` to `<Swig
76 Object ...>`.  How you go the other way?  You'll need this if you want
77 to do something extra fancy, like accessing the array members ;).
78
79     >>> vector.data[0]
80     Traceback (most recent call last):
81       ...
82     TypeError: 'SwigPyObject' object is not subscriptable
83
84 The answer here is the `.frompointer()` method, which can function as
85 a [class method][classmethod]:
86
87     >>> reconst_data = example.sample_array.frompointer(vector.data)
88     >>> reconst_data[n-1]
89     7.0
90
91 Or as a single line:
92
93     >>> example.sample_array.frompointer(vector.data)[n-1]
94     7.0
95
96 I chose the somewhat awkward name of `reconst_data` for the
97 reconstitued data, because if you use `data`, you clobber the earlier
98 `example.sample_array(n)` definition.  After the clobber, Python
99 garbage collects the old `data`, and becase the old data claims it
100 owns the underlying memory, Python frees the memory.  This leaves
101 `vector.data` and `reconst_data` pointing to unallocated memory, which
102 is probably not what you want.  If keeping references to the original
103 objects (like I did above with `data`) is too annoying, you have to
104 manually tweak the [ownership flag][ownership]:
105
106     >>> data.thisown
107     True
108     >>> data.thisown = False
109     >>> data = example.sample_array.frompointer(vector.data)
110     >>> data[n-1]
111     7.0
112
113 This way, when `data` is clobbered, SWIG doesn't release the
114 underlying array (because `data` no longer claims to own the array).
115 However, `vector` doesn't own the array either, so you'll have to
116 remember to reattach the array to somthing that will clean it up
117 before vector goes out of scope to avoid leaking memory:
118
119     >>> data.thisown = True
120     >>> del vector, data
121
122 For deeply nested structures, this can be annoying, but it will work.
123
124 <!-- test these doctests from the SWIG directory with
125   LD_LIBRARY_PATH=. nosetests --with-doctest --doctest-extension=mdwn ../SWIG.mdwn
126   -->
127
128 [[!tag tags/tools]]
129 [[!tag tags/C]]
130 [[!tag tags/Python]]
131
132 [SWIG]: http://www.swig.org/
133 [carrays.i]: http://www.swig.org/Doc2.0/Library.html#Library_carrays
134 [docs]: http://www.swig.org/Doc2.0/Library.html#Library_carrays
135 [comedilib-patch]: http://comedi.org/git?p=comedi/comedilib.git;a=blobdiff;f=swig/comedi.i;h=5da6160d91d206d007e20c9ac5091d1735afdd30;hp=581997542927fd31cd2e0d03c220377774cfa600;hb=3fe8e6baac051d80906c6fac6c18c04c8df9ce4a;hpb=880074831499ba68c17a1c2653d71d6eef3b9cfb
136 [classmethod]: http://docs.python.org/library/functions.html#classmethod
137 [ownership]: http://www.swig.org/Doc2.0/Python.html#Python_nn30