简体   繁体   English

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

[英]How to pass list of structs to C in SWIG/Python

I have a C++ class I'm exporting via swig, and a function that takes an array of Foo s: 我有一个C ++类,我通过swig导出,以及一个带有Foo数组的函数:

typedef class Foo {
  int i;
} Foo;

void func(Foo *all_foos);

Now I'd like to be able to pass a python list containing those into all_foos: 现在我希望能够将包含这些内容的python列表传递给all_foos:

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

I have a typemap which doesn't work. 我有一个不起作用的typemap。 See the FIXME line for where I need help. 请参阅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;
  }
}

Basically, I have a PyObject o; 基本上,我有一个PyObject o; I need to get the SwigPyObject from it (do I just cast it? Or is it a member?) and then get my Foo pointer from the SwigPyObject somehow. 我需要从中获取SwigPyObject (我只是转换它吗?或者它是一个成员?)然后以某种方式从SwigPyObject获取我的Foo指针。

Your example is a little confused because your C++ function takes Foo* , but your typemap takes Foo** (ie array of Foos vs array of pointer to Foos). 你的例子有点困惑,因为你的C ++函数需要Foo* ,但是你的typemap需要Foo** (即Foos数组与指向Foos的指针数组)。 I'm assuming you meant the latter, because that's the only sane way to tell how long the array is from the function declaration you've given. 我假设你的意思是后者,因为这是从你给出的函数声明中判断数组有多长的唯一理智方式。

In terms of the immediate question "how can I convert Python objects to C++ pointers of a given type?" 就直接问题而言“如何将Python对象转换为给定类型的C ++指针?” I usually solve that question by letting SWIG generate some code for me and then inspecting it. 我通常让SWIG为我生成一些代码然后检查它来解决这个问题。 So for example if you have a function void bar(Foo*); 例如,如果你有一个函数void bar(Foo*); then SWIG will generate some code in the wrapper: 然后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;
}

The interesting bit of that is the call to SWIG_ConvertPtr which is doing what you want. 有趣的是调用SWIG_ConvertPtr ,它正在做你想要的。 With that knowledge we just need to put it inside the loop that you've already written for your typemap, so your 'in' typemap becomes: 有了这些知识,我们只需要将它放在你已经为你的typemap编写的循环中,这样你的'in'类型映射就会变成:

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

Notice that in the interests of making the typemap generic and reusable I've replaced parts of it with special typemap variables - the same code gets generated as for the single example we saw, but you can reuse it slightly more. 请注意,为了使typemap具有通用性和可重用性,我已经使用特殊的typemap变量替换了它的部分内容 - 生成的代码与我们看到的单个示例相同,但您可以稍微重复使用它。

That's sufficient to compile and run the example code you gave (with the one change as noted), however there's a memory leak still. 这足以编译和运行您给出的示例代码(如上所述的一个更改),但仍然存在内存泄漏。 You call malloc() , but never free() , so we need to add a corresponding 'freearg' typemap : 你调用malloc() ,但从不free() ,所以我们需要添加一个相应的'freearg'类型映射

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

This gets called both on success and error, but that's fine because $1 is initalised to NULL, so the behavior is correct regardless of if we successfully malloc or not. 这在成功和错误时都被调用,但这很好,因为$1被初始化为NULL,所以无论我们是否成功malloc ,行为都是正确的。


As a general point since this is C++ I think your interface is designed wrong - there's no good reason not to use std::vector , std::list or similar which also makes the wrapping simpler. 作为一般性的一点,因为这是C ++我认为你的界面设计错误 - 没有充分的理由不使用std::vectorstd::list或类似的东西也使得包装更简单。 It's also weird style to use typedefs like you have in your header file. 在头文件中使用typedef也是一种奇怪的风格。

If it were me writing this though, I'd be looking to use RAII in the wrapper, even if I couldn't change the interface to use a container. 如果是我写这个,我会在包装器中使用RAII,即使我无法更改接口以使用容器。 So that means I'd write my 'in' typemap as: 这意味着我将我的'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");
  }
}

which then removes the need for a 'freearg' typemap and never leaks. 然后,它消除了对'freearg'类型映射的需求,从不泄漏。


If for some reason you don't want to write a custom typemap, or change the interface to use a type that already has good typemaps in the SWIG library you can still give your Python users an intuitive pythonic interface by using %rename to 'hide' the default implementation and %pythoncode to inject some additional Python with the same name that "massages" the Python input into the carrays interface transparently to the Python user. 如果由于某种原因您不想编写自定义类型映射,或者更改界面以使用SWIG库中已有良好类型映射的类型,您仍然可以通过使用%rename来隐藏Python用户直观的pythonic界面'默认实现和%pythoncode注入一些额外的Python,其名称与Python用户透明地“按摩”Python输入到carrays接口。

You should be able to do everything with standard "front end" SWIG tools: 您应该能够使用标准的“前端”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;
    }
%}

There shouldn't be need for typemap here. 这里不应该有typemap。

I would try with carrays.i , something like 我会尝试使用carrays.i ,类似于

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

void func(Foo *all_foos);

Then in python: 然后在python中:

fooArray = FooArray(10)
func(fooArray)

Typemaps should only be used if there is no other SWIG tool to achieve the API you want. 只有在没有其他SWIG工具才能实现所需的API时,才应使用类型图。 I think it might even be possible to do better than the above, with %inline (separate answer because very different from this). 我认为甚至可能比上面做的更好,使用%inline(单独的答案因为与此截然不同)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM