Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SWIG Python Structure Array

Tags:

python

c

swig

I've been searching for a few days trying to figure out how to turn an array of structures into a Python list. I have a function that returns a pointer to the beginning of the array.

struct foo {
    int member;
};

struct foo *bar() {
    struct foo *t = malloc(sizeof(struct foo) * 4);
    ... do stuff with the structs ...
    return t;
}

After calling the function from Python I get a single structure but trying to access the other elements of the array causes an error:

foo = bar()
print foo[1].member
TypeError: 'foo' object does not support indexing

I've tried using %array_class but to no avail. I've also tried defining the function as returning an array in the SWIG interface file:

extern struct foo [ANY] bar();

The SWIG documentation is pretty thorough but I can't seem to figure this out.

like image 859
chrisbisnett Avatar asked Aug 17 '12 00:08

chrisbisnett


1 Answers

The idea you tried with [ANY] won't work for several reasons. Primarily though ANY can be used in typemaps to allow the same typemap to work with varying fixed size arrays, which isn't what you've got there.

The syntax for C isn't quire right there either. You can't write:

int[4] bar() {
  static int data[4];
  return data;
}

Or:

int bar()[4] {
  static int data[4];
  return data;
}

In standard C. The closest you can get is:

int (*bar())[4] {
  static int data[4] = {1,2,3,4};
  return &data;
}

But that's not really any easier to wrap.

However, the simple solution can be made to work using %array_class, for example:

%module test

%inline %{
  struct foo {
    int member;
  };

  struct foo *bar() {
    struct foo *arr = malloc(sizeof(struct foo) * 4);
    for (int i = 0; i < 4; ++i) 
      arr[i].member = i;
    return arr;
  }
%}

%include <carrays.i>
%array_class(struct foo, fooArray);

This lets me do:

Python 3.2.3 (default, May  3 2012, 15:54:42) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> arr = test.fooArray.frompointer(test.bar())
>>> arr
<test.fooArray; proxy of <Swig Object of type 'fooArray *' at 0xb6f332a8> >
>>> arr[0]
<test.foo; proxy of <Swig Object of type 'struct foo *' at 0xb6f33038> >
>>> arr[1]
<test.foo; proxy of <Swig Object of type 'struct foo *' at 0xb6f33380> >
>>> arr[2]
<test.foo; proxy of <Swig Object of type 'struct foo *' at 0xb6f33398> >
>>> arr[3]
<test.foo; proxy of <Swig Object of type 'struct foo *' at 0xb6f330c8> >
>>> 

We can go one step better though (possibly) by injecting the code to to covert the pointer to the array type automatically, by adding the following before bar() is seen by SWIG:

%pythonappend bar() %{
    # Wrap it automatically
    val = fooArray.frompointer(val)
%}

So you can now use it like:

Python 3.2.3 (default, May  3 2012, 15:54:42) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> test.bar()[1].member
1
>>> arr = test.bar()
>>> arr[3].member
3

You need to be careful about memory ownership. In these examples so far the memory is leaked. You can use %newobject to tell SWIG that the memory is owned by the Python side, but then it will get released too early (as soon as the original return value is no longer referenced) so you would need to arrange to keep the original value around longer. A complete example of that, which saves the original pointer inside the instance of the array class to keep a reference around as long as the array wrapper itself would be:

%module test

%pythonappend bar() %{
    # Wrap it automatically
    newval = fooArray.frompointer(val)
    newval.ptr_retain = val
    val = newval
%}

%newobject bar();

%inline %{
  struct foo {
    int member;
  };

  struct foo *bar() {
    struct foo *arr = malloc(sizeof(struct foo) * 4);
    for (int i = 0; i < 4; ++i) 
      arr[i].member = i;
    return arr;
  }
%}

%include <carrays.i>
%array_class(struct foo, fooArray);

Notice though that the array class this generates is unbounded, exactly like a struct foo* in C. This means you can't iterate over it in Python - the size is unknown. If the size is genuinely fixed, or you have a way of knowing the size somehow you can wrap this in a much nicer (in my view) way by writing a typemap that returns a PyList. It's a bit more work to write, but makes the interface seem nicer on the Python side.

like image 108
Flexo Avatar answered Nov 01 '22 17:11

Flexo