如何在 SWIG/Python 中将结构列表传递给 C

Posted

技术标签:

【中文标题】如何在 SWIG/Python 中将结构列表传递给 C【英文标题】:How to pass list of structs to C in SWIG/Python 【发布时间】:2014-02-07 21:04:55 【问题描述】:

我有一个通过 swig 导出的 C++ 类,以及一个接受 Foos 数组的函数:

typedef class Foo 
  int i;
 Foo;

void func(Foo *all_foos);

现在我希望能够将包含这些内容的 python 列表传递给 all_foos:

afoo = mymod.Foo()
bfoo = mymod.Foo()
mymod.func([afoo, bfoo])

我有一个不起作用的类型映射。请参阅 FIXME 行了解我需要帮助的地方。

%typemap(in) Foo ** 
  /* Check if it's a list */
  if (PyList_Check($input)) 
    int size = PyList_Size($input);
    int i = 0;
    $1 = (Foo **) malloc((size+1)*sizeof(Foo *));
    for (i = 0; i < size; i++) 
      PyObject *o = PyList_GetItem($input,i);
      // here o->ob_type->tp_name is "Foo"; could check that
      // FIXME: HOW DO I GO FROM o -> SwigPyObject -> Foo *?  THIS IS WRONG
      $1[i] = (Foo *)(reinterpret_cast<SwigPyObject *>(o))->ptr;
    
   else 
    PyErr_SetString(PyExc_TypeError,"not a list");
    return NULL;
  

基本上,我有一个PyObject o;我需要从中获取SwigPyObject(我只是强制转换它吗?还是它是一个成员?)然后以某种方式从SwigPyObject 获取我的Foo 指针。

【问题讨论】:

我想我找到了一个解决方案,只是在我生成的一些包装器代码中四处寻找:(Foo *)SWIG_Python_GetSwigThis(o)-&gt;ptr 似乎从 o 变为正确有效的 Foo *。可以吗,还是我在滥用一些内部 SWIG API? 【参考方案1】:

您的示例有点混乱,因为您的 C++ 函数采用 Foo*,但您的类型映射采用 Foo**(即 Foos 数组与指向 Foos 的指针数组)。我假设您的意思是后者,因为这是判断数组距离您给出的函数声明多长时间的唯一明智方法。

就直接问题而言“如何将 Python 对象转换为给定类型的 C++ 指针?”我通常通过让 SWIG 为我生成一些代码然后检查它来解决这个问题。因此,例如,如果您有一个函数 void bar(Foo*);,那么 SWIG 将在包装器中生成一些代码:

SWIGINTERN PyObject *_wrap_bar(PyObject *SWIGUNUSEDPARM(self), PyObject *args) 
  PyObject *resultobj = 0;
  Foo *arg1 = (Foo *) 0 ;
  void *argp1 = 0 ;
  int res1 = 0 ;
  PyObject * obj0 = 0 ;

  if (!PyArg_ParseTuple(args,(char *)"O:bar",&obj0)) SWIG_fail;
  res1 = SWIG_ConvertPtr(obj0, &argp1,SWIGTYPE_p_Foo, 0 |  0 );
  if (!SWIG_IsOK(res1)) 
    SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "bar" "', argument " "1"" of type '" "Foo *""'");
  
  arg1 = reinterpret_cast< Foo * >(argp1);
  bar(arg1);
  resultobj = SWIG_Py_Void();
  return resultobj;
fail:
  return NULL;

其中有趣的一点是对SWIG_ConvertPtr 的调用,它正在做你想做的事。有了这些知识,我们只需要将它放在您已经为您的类型映射编写的循环中,因此您的“输入”类型映射变为:

%typemap(in) Foo ** 
  $1 = NULL;
  if (PyList_Check($input)) 
    const size_t size = PyList_Size($input);
    $1 = (Foo**)malloc((size+1) * sizeof(Foo*));
    for (int i = 0; i < size; ++i) 
      void *argp = 0 ;
      const int res = SWIG_ConvertPtr(PyList_GetItem($input, i), &argp, $*1_descriptor, 0);
      if (!SWIG_IsOK(res)) 
        SWIG_exception_fail(SWIG_ArgError(res), "in method '" "$symname" "', argument " "$argnum"" of type '" "$1_type""'");
      
      $1[i] = reinterpret_cast<Foo*>(argp);
    
    $1[size] = NULL;
  
  else 
    // Raise exception
    SWIG_exception_fail(SWIG_TypeError, "Expected list in $symname");
  

请注意,为了使类型映射具有通用性和可重用性,我已将其部分替换为 special typemap variables - 生成的代码与我们看到的单个示例相同,但您可以稍微重复使用它。

这足以编译和运行您提供的示例代码(如上所述进行了一项更改),但是仍然存在内存泄漏。你调用malloc(),但从来没有free(),所以我们需要添加一个对应的'freearg' typemap:

%typemap(freearg) Foo ** 
  free($1);

这在成功和错误时都会被调用,但这很好,因为$1 被初始化为 NULL,所以无论我们是否成功malloc,行为都是正确的。


一般来说,因为这是 C++,我认为您的界面设计错误 - 没有充分的理由不使用 std::vectorstd::list 或类似的东西,这也使包装更简单。使用头文件中的 typedef 也是一种奇怪的风格。

如果是我写这篇文章,我会希望在包装器中使用 RAII,即使我无法更改接口以使用容器。所以这意味着我会将我的“in”类型映射写为:

%typemap(in) Foo ** (std::vector<Foo*> temp) 
  if (PyList_Check($input)) 
    const size_t size = PyList_Size($input);
    temp.resize(size+1);
    for (int i = 0; i < size; ++i) 
      void *argp = 0 ;
      const int res = SWIG_ConvertPtr(PyList_GetItem($input, i), &argp, $*1_descriptor, 0);
      if (!SWIG_IsOK(res)) 
        SWIG_exception_fail(SWIG_ArgError(res), "in method '" "$symname" "', argument " "$argnum"" of type '" "$1_type""'");
      
      temp[i] = reinterpret_cast<Foo*>(argp);
    
    temp[size] = NULL;
    $1 = &temp[0]; // Always valid since we +1
  
  else 
    // Raise exception
    SWIG_exception_fail(SWIG_TypeError, "Expected list in $symname");
  

这样就不再需要 'freearg' 类型映射并且永远不会泄漏。


如果由于某种原因您不想编写自定义类型映射,或更改界面以使用 SWIG 库中已经具有良好类型映射的类型,您仍然可以使用 @987654337 为 Python 用户提供直观的 Pythonic 界面@“隐藏”默认实现,%pythoncode 注入一些额外的 Python,其名称相同,将 Python 输入“按摩”到对 Python 用户透明的 carrays 接口中。

【讨论】:

这是完美的——超出了我的需要,但我一定会使用它。使用临时向量的想法很好,谢谢!实际上它对我的帮助更大,因为在我的真实代码中,每个元素都需要一个 aux ptr(一个 py_buffer 视图),这使我的 freearg 变得复杂——我不再需要这种方式了。【参考方案2】:

您应该能够使用标准的“前端”SWIG 工具完成所有工作:

%include <std_list.i>

%ignore func
%rename(func) funcWrap

namespace std 
   %template(FooList) std::list<Foo*>;


%include "Foo.h"

%inline %
    // wrap with const list of non-const Foo*
    void funcWrap(const std::list<Foo *>& all_foos)
    
         // create Foo* array: 
         Foo* fooArray = new Foo(all_foos.size());
         int count = 0;
         for (std::list<Foo *>::const_iterator ii=all_foos.begin(); ...) 
              fooArray[count] = *ii;
         func(fooArray);
         delete fooArray;
    
%

这里应该不需要typemap。

【讨论】:

【参考方案3】:

我会尝试carrays.i,类似

%include carrays.i
%array_class(Foo, FooArray)

void func(Foo *all_foos);

然后在python中:

fooArray = FooArray(10)
func(fooArray)

只有在没有其他 SWIG 工具来实现您想要的 API 时才应使用类型映射。我认为使用 %inline 甚至可能比上述做得更好(单独的答案,因为与此非常不同)。

【讨论】:

我不同意你关于类型映射的说法——它们应该在需要时编写,以便为目标语言用户提供最直观的体验。 @flexo 我的措辞有点过于简洁,因为我同意,希望现在更好。 我同意@Flexo,我宁愿给我的用户一个更pythonic的界面。对 Python 程序员来说,数组有点难看。

以上是关于如何在 SWIG/Python 中将结构列表传递给 C的主要内容,如果未能解决你的问题,请参考以下文章

如何在反应js中将graphql列表传递给apollo中的突变

如何在spring mvc中将嵌套列表元素传递给控制器

如何在pybind11中将共享指针列表传递给c++对象

如何在Vue2中将数据从父级传递给子级

如何在模板中将数据从 Flask 传递到 JavaScript?

在 Django 中将值传递给 Bootstrap 模式