7

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.

Flexo
  • 87,323
  • 22
  • 191
  • 272
chrisbisnett
  • 83
  • 1
  • 4
  • 1
    Have you looked here? - http://stackoverflow.com/questions/8114030/swig-python-array-inside-structure – dpandiar Aug 17 '12 at 02:33
  • @dpandiar - that's quite a different case because the size is fixed and known and the arrays are members and not return values from a function – Flexo Aug 17 '12 at 10:12

1 Answers1

10

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.

Flexo
  • 87,323
  • 22
  • 191
  • 272
  • Thank you so much! This is exactly what I was looking for and worked perfectly. I do know the size of the array from a calculation that I didn't include to simplify the example. I looked at the documentation on carrays.i before but I was confused by this: "When using this macro, type is restricted to a simple type name like int, float, or Foo." I kept trying to use the type `struct foo*` and when it didn't work assumed that it would only work for basic c types. – chrisbisnett Aug 17 '12 at 23:08