使用SWIG将向量作为内存视图 [英] Expose a vector as a memoryview using SWIG

查看:141
本文介绍了使用SWIG将向量作为内存视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个头文件如:

  #include< vector& 

inline std :: vector< uint8_t>& vec(){
static std :: vector< uint8_t> v {'a','b','c','d'};
return v;
}

inline const std :: vector< uint8_t>& cvec(){
return vec();
}

我可以使用std_vector.i和pyabc.i在SWIG中包装它,但是这是非常低效的(有一个跳C ++和Python代码为每个访问),并且假定这些字面上只是一堆字节我应该能够包装他们与 Python的 memoryview 界面



std :: vector< uint8_t> 作为Python memoryview

c>首先需要创建一个 Py_buffer 。在Python 3.3+中有一个方便的帮助函数, PyMemoryView_FromMemory 为我们做了很多工作。在早期版本中,虽然我们需要额外的一些步骤,所以我们的基本输出框如下:

 %typemap(out)std :: vector< uint8_t>& const std :: vector< uint8_t> {
Py_buffer * buf =(Py_buffer *)malloc(sizeof * buf);
const bool ro = info< $ 1_type> :: is_readonly();
if(PyBuffer_FillInfo(buf,NULL,&((* $ 1)[0]),(* $ 1).size(),ro,PyBUF_ND)){
//错误,句柄
}
$ result = PyMemoryView_FromBuffer(buf);
}

这里我们基本上为 Py_buffer 。这只包含Python内部缓冲区的详细信息。我们分配的内存将由 memoryview 对象创建后拥有。不幸的是,因为它将通过调用 free()释放,我们需要用 malloc()



除了 Py_buffer 和一个可选的 Py_Object PyBuffer_FillInfo 需要一个 void * (缓冲区本身),缓冲区的大小,一个布尔值,表示它是否可写和一个标志。在这种情况下,我们的标志只是表示我们为缓冲区提供了C型连续内存。



为了决定它是否只读,我们使用SWIG的内置 $ n_type 变量和助手(可能如果我们想要一个C ++ 11类型trait)。



要完成我们的SWIG接口,我们需要提供该帮助并包括头文件,所以整个事情变得:

 %module test 

%{
#includetest.hh

namespace {
template< typename T>
struct info {
static bool is_readonly(){
return false;
}
};

template< typename T>
struct info< const T&> {
static bool is_readonly(){
return true;
}
};
}
%}

%typemap(out)std :: vector< uint8_t>& const std :: vector< uint8_t> {
Py_buffer * buf =(Py_buffer *)malloc(sizeof * buf);
const bool ro = info< $ 1_type> :: is_readonly();
if(PyBuffer_FillInfo(buf,NULL,&((* $ 1)[0]),(* $ 1).size(),ro,PyBUF_ND)){
//错误,句柄
}
$ result = PyMemoryView_FromBuffer(buf);
}

%includetest.hh



然后可以用以下方法测试它:

  import test 

print test.vec()
打印len(test.vec())
打印test.vec()[0]
打印test.vec()。readonly
test.vec()[0]
print test.vec()[0]

print这应该失败:
test.cvec()[0] = 0



它的工作原理是使用Python 2.7测试。



它使用std_vector.i这种方法确实有一些缺点。最大的是,我们不能调整矢量的大小,或将其转换回一个矢量后来微不足道。我们可以解决这个问题,至少部分地通过创建像正常的向量的SWIG代理,并使用 PyBuffer_FillInfo 的第二个参数在内部存储它。 (如果我们必须管理向量的所有权,这也将是需要的)。


I have a header file like:

#include <vector>

inline std::vector<uint8_t>& vec() {
  static std::vector<uint8_t> v { 'a', 'b', 'c', 'd' };
  return v;
}

inline const std::vector<uint8_t>& cvec() {
  return vec();
}

I can wrap it in SWIG using std_vector.i and pyabc.i but that is quite inefficient (there's a jump between C++ and Python code for every access) and given that these are literally just a bunch of bytes I ought to be able to wrap them with Python's memoryview interface.

How can I expose my std::vector<uint8_t> as a Python memoryview?

解决方案

Exposing it as a memoryview requires creating a Py_buffer first. In Python 3.3+ there is a convenient helper function, PyMemoryView_FromMemory that does a lot of the work for us. In earlier versions though we'll need to take a few extra steps, so our basic out typemap looks like:

%typemap(out) std::vector<uint8_t>&, const std::vector<uint8_t>& {
  Py_buffer *buf=(Py_buffer*)malloc(sizeof *buf);
  const bool ro = info<$1_type>::is_readonly();
  if (PyBuffer_FillInfo(buf, NULL,  &((*$1)[0]), (*$1).size(), ro, PyBUF_ND)) {
    // error, handle
  }
  $result = PyMemoryView_FromBuffer(buf);
}

Here we're basically allocating some memory for the Py_buffer. This just contains the details of the buffer internally for Python. The memory we allocate will be owned by the memoryview object once it's created. Unfortunately since it's going to be released with a call to free() we need to allocate it with malloc(), even though it's C++ code.

Besides the Py_buffer and an optional Py_Object PyBuffer_FillInfo takes a void* (the buffer itself), the size of the buffer, a boolean indicating if it's writeable and a flag. In this case our flag simply indicates that we have provided C-style contiguous memory for the buffer.

For deciding if it is readonly or not we used SWIG's built in $n_type variable and a helper (which could be a C++11 type trait if we wanted).

To complete our SWIG interface we need to provide that helper and include the header file, so the whole thing becomes:

%module test

%{
#include "test.hh" 

namespace {
  template <typename T>
  struct info {
    static bool is_readonly() {
      return false;
    }
  };

  template <typename T>
  struct info<const T&> {
    static bool is_readonly() {
      return true;
    }
  };
}
%}

%typemap(out) std::vector<uint8_t>&, const std::vector<uint8_t>& {
  Py_buffer *buf=(Py_buffer*)malloc(sizeof *buf);
  const bool ro = info<$1_type>::is_readonly();
  if (PyBuffer_FillInfo(buf, NULL,  &((*$1)[0]), (*$1).size(), ro, PyBUF_ND)) {
    // error, handle
  }
  $result = PyMemoryView_FromBuffer(buf);
}

%include "test.hh"

We can then test it with:

import test

print test.vec()
print len(test.vec())
print test.vec()[0]
print test.vec().readonly
test.vec()[0]='z'
print test.vec()[0]

print "This should fail:"
test.cvec()[0] = 0

Which worked as expected, tested using Python 2.7.

Compared to just wrapping it using std_vector.i this approach does have some drawbacks. The biggest being that we can't resize the vector, or convert it back to a vector later trivially. We could work around that, at least partially by creating a SWIG proxy for the vector like normal and using the second parameter of PyBuffer_FillInfo to store it internally. (This would also be needed if we had to manage the ownership of the vector for instance).

这篇关于使用SWIG将向量作为内存视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆