cython wrap cpp 结构和函数,参数为结构数组

Posted

技术标签:

【中文标题】cython wrap cpp 结构和函数,参数为结构数组【英文标题】:cython wrap cpp structs and function with parameter an array of structs 【发布时间】:2019-02-12 15:20:23 【问题描述】:

我正在使用 Cython 在 python 中包装一个 c++ 库。不幸的是,我无法访问 c++ 库。因此,我必须找到一种方法来包装 lib API 所公开的结构和函数。

我的问题是关于包装 c++ 结构的最佳方式;随后,如何在 python 中创建内存视图并将其指针(第一个元素地址)传递给 c++ 函数,参数为 cpp 结构数组。

例如,假设我有以下 h 文件:

//test.h

struct cxxTestData

   int m_id;
   double m_value;
;

void processData(cxxTestData* array_of_test_data, int isizeArr)

我的 pyx 文件如下所示

cdef extern from "Test.h":
    cdef struct cxxTestData:
        int m_id
        double m_value

cdef class pyTestData:
    cdef cxxTestData cstr

    def __init__(self, id, value):
        self.cstr.m_id = id
        self.cstr.m_value = value

    @property
    def ID(self):
        return self.cstr.m_id

    @property
    def Value(self):
        return self.cstr.m_value

现在,我想创建一些 pyTestData 并将它们存储在一个 dtype 对象数组中。然后我想将此数组作为 cython/python 函数中的内存视图传递。

包装函数将具有以下签名

cpdef void pyProcessData(pyTestData[::1] test_data_arr)

我已经测试了上面的内容,它编译成功。我还设法修改了每个结构的成员。然而,这不是我想要达到的目标。我的问题是如何从这一点开始传递一个数组,其中包含封装在每个 pyTestData 对象中的 c++ 结构(通过 self.cstr)。

作为示例,请查看以下列表:

cpdef void pyProcessData(pyTestData[::1] test_data_arr):
    cdef int isize test_data_arr.shape[0]

    # here I want to create/insert an array of type cxxTestData to pass it 
    # to the cpp function
    # In other words, I want to create an array of [test_data_arr.cstr]
    # I guess I can use cxxTestData[::1] or cxxTestData* via malloc and
    # copy each test_data_arr[i].cstr to this new array
    cdef cxxTestData* testarray = <cxxTestData*>malloc(isize*sizeof(cxxTestData))

    cdef int i
    for i in range(isize):
        testarray[i] = test_data_arr[i].cstr
    processData(&testarray[0], isize)

    for i in range(isize):
        arrcntrs[i].pystr = testarray[i]

    free(testarray)

有人遇到过这种情况吗?有没有更好的方法可以在上述函数中传递我的 python 对象,而不必在内部复制 cxx 结构?

非常感谢,如果我做了根本性的错误,我们深表歉意。

【问题讨论】:

1) 我不认为pyTestData[::1] 真的有效——它实际上会毫无怨言地接受objects 的任何数组。我确信它根本没有用于编译,所以编译而不做你想要的感觉就像回归。 2)根本问题是pyTestData是Python对象,所以必须单独分配并且还包含Python引用计数数据。因此,没有可靠的 c++ 对象块供您发送到您的函数(无需复制)。 非常感谢您的回答。实际上, pyTestData[::1] 确实可以编译。但是,我同意它接受任何对象数组。因此,不是存储我的对象的最佳选择。我是否必须明确定义 dtype (pyTestDataType = [('m_id', 'int'), ('m_value', 'double')]) 然后将其用于我的签名以便只接受这些对象?你能举个例子吗?这不是真正的问题。但是,它可以帮助我做所有正确的事情。 很遗憾,我并没有很好的解决方案... 【参考方案1】:

由于您希望将 cxxTestData 的数组传递给您的 C++ 函数,因此最好将其分配为数组。一些说明该方法的未经测试的代码:

cdef class TestDataArray:
    cdef cxxTestData* array:
    def __init__(self, int length):
        self.array = <cxxTestData*>calloc(length,sizeof(cxxTestData))
    def __dealloc__(self):
        free(self.array)
    def __getitem__(self, int idx):
        return PyTestData.from_pointer(&self.array[idx],self) # see later
    def __setitem__(self, int idx, PyTestData pyobj): # this is optional
        self.array[idx] = deref(pyobj.cstr)

然后,您需要稍微修改您的PyTestData 类,使其拥有一个指针,而不是直接拥有该类。它还应该有一个表示数据最终所有者的字段(例如数组)。这样可以确保数组保持活动状态,并且还可以允许 PyTestData 拥有自己的数据的情况:

cdef class PyTestData:
    cdef cxxTestData* cstr
    cdef object owner

    def __init__(self, id, value):
        self.owner = None
        self.cstr = <cxxTestData*>malloc(sizeof(cxxTestData))
        self.cstr.m_id = id
        self.cstr.m_value = value

    def __dealloc__(self):
        if self.owner is None: # i.e. this class owns it
             free(self.cstr)

    @staticmethod
    cdef PyTestData from_pointer(cxxTestData* ptr, owner):
        # calling __new__ avoids calling the constructor
        cdef PyTestData x = PyTestData.__new__(PyTestData)
        x.owner = owner
        x.cstr = ptr
        return x

创建TestDataArray 类需要付出一些额外的努力,但它以可直接从 C++ 使用的格式存储数据,因此我认为这是最好的解决方案。

【讨论】:

嗨,David,非常感谢您提供的说明性示例。那么,TestDataArray 是一个额外的包装类,用于存储我的 cxx 结构,对吧?并且使用 setitem 我可以填充我的数组。 是的,TestDataArray 是一个额外的包装器。我可能会使用__getitem__,然后更改数据而不是使用__setitem__(只是因为__setitem__ 涉及更多复制,而您似乎试图避免这种情况)。 如何用 getitem 填充数组?我想我必须使用 setter 方法来填充我的 cxxTestData 数组。 当您调用__getitem__ 时,它会返回一个与数组的一部分共享数据的PyTestData。因此,如果您更改 PyTestData 的元素,那么它也会更改数组。 我明白了。但是,在我的情况下,我需要用一些 instatiated pyTestData 对象复制/填充我的数组,然后将数组传递给求解器 (processData)。我测试了你的代码,它工作得非常好。我将只编辑一些小部分。请查阅。再次感谢您的宝贵建议。

以上是关于cython wrap cpp 结构和函数,参数为结构数组的主要内容,如果未能解决你的问题,请参考以下文章

Cython 和 SIMD 内在函数:防止 SIMD 内在函数的参数转换为 python 对象

cython - 包装一个 cpp 类聚合另一个

c_cpp 其中timer_wrap的Start函数设置为'start',可以从JS访问

分发 Cython 生成的 cpp 文件

在 cython 中处理默认参数

将带有字符串的结构化 numpy 数组传递给 cython 函数