SWIG Python结构数组

7

我已经搜索了几天,试图弄清楚如何将结构数组转换为Python列表。我有一个返回指向数组开头的指针的函数。

struct foo {
    int member;
};

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

在从Python调用函数后,我得到了一个单独的结构体,但尝试访问数组的其他元素会导致错误:
foo = bar()
print foo[1].member
TypeError: 'foo' object does not support indexing

我尝试使用%array_class,但没有成功。我还尝试在SWIG接口文件中将函数定义为返回数组:

extern struct foo [ANY] bar();

SWIG文档非常详尽,但我似乎无法弄清楚这个问题。


1
你看过这里吗?- https://dev59.com/yV3Ua4cB1Zd3GeqP-DDM - dpandiar
@dpandiar - 这是一个非常不同的情况,因为大小是固定而已知的,数组是成员,而不是从函数返回的返回值。 - Flexo
1个回答

10

你尝试使用的 [ANY] 的想法由于多种原因而不起作用。主要是,ANY 可以用于 typemaps 中,以允许相同的 typemap 适用于不同的固定大小数组,但这并不适用于你所拥有的情况。

C 的语法也不是很正确。你不能写成:

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

或者:

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

在标准C中,你能得到的最接近的是:

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

但这样做其实也不容易解决问题。

然而,可以使用%array_class来实现简单的解决方案,例如:

%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);

这让我可以做到:

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> >
>>> 
我们可以更上一层楼(可能),通过在 SWIG 看到 bar() 之前添加以下内容来注入代码以自动将指针转换为数组类型:
%pythonappend bar() %{
    # Wrap it automatically
    val = fooArray.frompointer(val)
%}

现在你可以像这样使用它:

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

在这些例子中,你需要注意内存所有权的问题。目前为止,内存泄漏了。你可以使用%newobject告诉SWIG,这块内存由Python方面拥有,但是这样会导致内存过早释放(一旦原始返回值不再被引用),因此你需要安排让原始值保留更久时间。一个完整的示例,它将原始指针保存在数组类实例中,以便在数组包装器本身存在时保持引用:

%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);

需要注意的是,该生成的数组类是无限制的,就像在C语言中的struct foo*一样。这意味着你无法在Python中对其进行迭代-大小是未知的。如果大小确实是固定的,或者你有某种方法来知道大小,那么可以编写一个类型映射,返回一个PyList,以更好地包装它(在我看来)。虽然这需要更多的工作来编写,但在Python方面使接口更加友好。


非常感谢!这正是我在寻找的,并且完美地运行。我知道数组的大小是通过一个我没有包含在简化示例中的计算得出的。我之前查看了carrays.i的文档,但是我被这个困惑了:“使用此宏时,类型限制为像int、float或Foo这样的简单类型名称。”我一直试图使用类型struct foo*,当它不起作用时,我假设它只能用于基本的c类型。 - chrisbisnett

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接