5

我有一个像这样的头文件:

#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

4

1 回答 1

9

将其公开memoryview需要创建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)) {
    // error, handle
  }
  $result = PyMemoryView_FromBuffer(buf);
}

在这里,我们基本上是为Py_buffer. 这仅包含 Python 内部缓冲区的详细信息。memoryview一旦创建,我们分配的内存将归对象所有。不幸的是,由于它将通过调用来发布,free()我们需要用 分配它malloc(),即使它是 C++ 代码。

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

为了确定它是否是只读的,我们使用了 SWIG 的内置$n_type变量和一个帮助器(如果我们愿意,它可以是 C++11 类型特征)。

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

%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"

然后我们可以使用以下方法对其进行测试:

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

它按预期工作,使用 Python 2.7 进行了测试。

与仅使用 std_vector.i 包装它相比,这种方法确实有一些缺点。最大的问题是我们无法调整向量的大小,或者稍后将其转换回向量。我们可以解决这个问题,至少部分是通过为正常向量创建一个 SWIG 代理并使用第二个参数在PyBuffer_FillInfo内部存储它。(例如,如果我们必须管理向量的所有权,这也是需要的)。

于 2013-06-08T10:41:30.327 回答