【问题标题】:Wrapping a function that takes an array of pointers包装一个带有指针数组的函数
【发布时间】:2012-05-06 21:13:29
【问题描述】:

我定义了一个struct a { }。我的 C 函数通过引用获取 struct a 数组,然后在其中填充数据。所以它接受一个参数struct a **。我想使用 SWIG 接口从 Python 调用此函数。有没有办法做到这一点?

【问题讨论】:

    标签: python c interface swig


    【解决方案1】:

    您可以使用 SWIG 和 Python 做到这一点。我已经设置了以下 test.h 文件来演示:

    struct a {
      int val;
    };
    
    static void populate(struct a **list) {
      int count = 0;
      // Terminate when NULL entry found
      while (*list) {
        (*list)->val = count++;
        ++list;
      }
    }
    

    populate 函数采用您所描述的 struct a**,假设列表以 NULL 结尾并对列表的每个元素执行某些操作。

    我选择将它暴露给 Python 的方式是作为一个函数,它接受一个整数,即要使用的列表的大小,然后返回结果列表,因为这是映射“通过参数返回”在我看来设置。

    我设置了一个基本的模块文件:

    %module test
    
    %{
    #include "test.h"
    %}
    

    然后添加了一个类型映射,该类型映射根据指定给 Python 函数的大小准备输入列表:

    %typemap(in,numinputs=1) struct a ** (int len=0) {
      len = (int)PyInt_AsLong($input);
      $1 = malloc(sizeof(struct a*)*(len+1));
      $1[len] = NULL;
      for (int i = 0; i < len; ++i) {
        $1[i] = malloc(sizeof(struct a));
      }
    }
    

    基本上,它将参数视为一个整数,为指针数组分配内存,然后为元素本身指向的对象分配一些内存。 (我单独分配它们以便 SWIG/Python 可以单独处理每个项目的引用计数,这比为所有元素分配一个块更简单)

    编写完该类型图后,我添加了另一个类型图,它负责获取调用函数的结果并将其转换回 PyList:

    %typemap(argout) struct a ** {
      // Push into PyList for return
      $result = PyList_New(len$argnum);
      for (int i = 0; i < len$argnum; ++i) {
         PyObject *element = SWIG_NewPointerObj(SWIG_as_voidptr($1[i]), SWIGTYPE_p_a, SWIG_POINTER_OWN);
         PyList_SET_ITEM($result, i, element);
      }
    }
    

    它利用了我们在 in 类型映射中创建的 len 变量,尽管由于它是 NULL 终止的,我们可以再次计算长度。然后,我们为传递给populate 函数的每个struct a 填充一个包装对象(由SWIG/Python 拥有)的Python 列表中的每个项目。请注意,如果类型名称不同,您需要将SWIGTYPE_p_a 更改为适当的SWIGTYPE。 (这些可以从生成的包装源中找到)。

    最后,我们需要为 C 端的列表释放内存,使用:

    %typemap(freearg) struct a ** {
      free($1);
    }
    

    然后让 SWIG 使用这些类型映射来包装头文件本身:

    %include "test.h"
    

    我编译了这个:

    痛饮 -python -Wall test.i gcc -Wall -Wextra test_wrap.c -I/usr/include/python2.6 -o _test.so -std=c99 -shared

    然后运行以下 Python 进行检查:

    import test
    
    r=test.populate(100)
    print r[0].val
    

    这个例子有几点需要注意:

    • 没有错误检查,例如test.populate("Hello world") 会做坏事,PyList_New 可能会失败,如果确实失败了,我们需要释放单个元素,而不仅仅是列表
    • 如果内存所有权语义不同,则需要适当更改 SWIG_POINTER_OWN
    • 如果您不想在 Python 接口上指定列表的大小(例如,它是已知的或以其他方式固定),您可以将 numinputs=1 更改为 0 并在 in 类型映射中相应地设置 len .
    • 如果您的函数采用列表和指定其长度的整数而不是使用以 NULL 结尾的列表,您可以将其扩展为 multi-argument typemap

    编译/运行此示例所需的所有代码都包含在此答案中,但如果您希望将其放在一个方便的 tarball 中,我也将其放在 on my site 中。即使该链接应该断开,答案仍然有效。

    【讨论】:

    • 感谢您的回答。我希望我在几天前找到了这个问题/答案。我不得不开发类似的东西,并想出了与您几乎相同的解决方案。另一个教训,大多数技术问题之前已经解决了......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-14
    • 1970-01-01
    • 1970-01-01
    • 2020-01-12
    • 1970-01-01
    相关资源
    最近更新 更多