包装采用 char** [in/out] 的 C 函数调用,以在 cython 中返回 python 列表

Posted

技术标签:

【中文标题】包装采用 char** [in/out] 的 C 函数调用,以在 cython 中返回 python 列表【英文标题】:Wrapping a C function call taking char** [in/out], to return a python list in cython 【发布时间】:2012-08-03 22:47:42 【问题描述】:

如何包装一个需要预先分配的char** 作为参数来存储结果的 C 函数调用?我正在尝试返回一个 python 列表结果。

我找到了相反的例子,还有this ctypes example,但我不完全确定 ctypes 是 cython 中的合适方法。

作为参考,我正在练习包装openni 库:http://openni.org/Documentation/Reference/classxn_1_1_pose_detection_capability.html

我包装的原始 C 签名是(它实际上是一个 C++ 方法,只是在内部包装了一个 C 函数):

/**
 * @brief Gets the names of all poses supported by this capability.

 * @param [out]     pstrPoses   Pre-allocated memory for the names of the supported poses.
 * @param [in,out]  nPoses      In input - size of the preallocated memory, in output
 *                              - the number of pose names.
 */

XnStatus GetAvailablePoses(XnChar** pstrPoses, XnUInt32& nPoses) const

XnChar 只是char 的类型定义)

这是我到目前为止的尝试,但它崩溃了:

from libc.stdlib cimport malloc, free

def get_available_poses(self):
    cdef: 
        int i 
        bytes name 
        XnStatus stat
        XnUInt32 size = self.handle.GetNumberOfPoses()
        XnChar **buf = <XnChar**>malloc(size * sizeof(XnChar*))

    if not buf:
        raise MemoryError()

    try:
        # this crashes: Segmentation fault
        stat = self.handle.GetAvailablePoses(buf, size)

        # if I could get to here, I would want to 
        # build a list to return (not saying this is
            # even correct either)
        for i in range(size):
            name = <char*>(buf[i])
            ...

    finally:
        free(buf)

该版本的 C 函数在技术上已被弃用,但新版本对我来说看起来更可怕:

/**
 * Gets the names of all poses supported by this capability.

 * @param [out]     pstrPoses       Pre-allocated memory for the names of the supported poses.
 * @param [in]      nNameLength     Memory size for each pose name.
 * @param [in,out]  nPoses          In input - size of the preallocated memory, in output
 *                                  - the number of pose names.
 */

 XnStatus GetAllAvailablePoses(XnChar** pstrPoses, XnUInt32 nNameLength, 
                                XnUInt32& nPoses) const;

理想情况下,如果我能弄清楚如何传入正确的 char** 并生成一个列表,我会使用新的,它还需要我指定分配的名称的长度。

更新

我将这个问题简化为基本问题,以确保我一开始就做对了:

源代码:

//chars.h

void setChars(char** str_array, int size);

//chars.cc

#include "chars.h"

void setChars(char** str_array, int size) 
    for (int i = 0; i < size; i++) 
        char *s = "FOO";
        str_array[i] = s;
    

赛通:

#chars.pxd

cdef extern from "chars.h":
    void setChars(char**, int)

#chars.pyx

from libc.stdlib cimport malloc, free

def py_setChars():
    cdef: 
        bytes s
        int i
        int size = 6
        char** buf = <char**>malloc(size * sizeof(char*))

    if not buf:
        raise MemoryError()

    out = []

    try:
        setChars(buf, size)

        for i in range(size):
            s = buf[i]
            out.append(s)

    finally:
        free(buf)

    return out

它按预期工作:

In [1]: import chars
In [2]: chars.py_setChars()
Out[2]: ['FOO', 'FOO', 'FOO', 'FOO', 'FOO', 'FOO']

我猜想我想使用的 GetAllAvailablePoses() 调用期待某种我没有正确执行的预分配内存,因此参数要求每个字符的大小。

【问题讨论】:

你确定你预分配的东西是正确的吗?它只想要一个未初始化的char*s 的预分配数组,而不是一组指向预分配字符串缓冲区的char*s 的预分配数组?或者甚至只是一个 NULL 初始化的 char*s 数组? (另外,由于您显然是在尝试包装一些不是您编写的库,也许您想告诉我们是哪一个,以便我们查看文档。) 你确定它在你认为它崩溃的地方崩溃了吗?在我看来, name = (buf[i]) 出现在 free(buf) 之后。 @abarnert:我实际上不确定任何事情,这就是我导致崩溃的原因:-) 这是我尝试过的几种方法的一个例子,但没有运气。但我刚刚用 openni 文档参考更新了这个问题。 @user237182:哎呀!这是被注释掉的混合内容的错误粘贴。我只是把那个块移到了 try 中。但我 100% 确定是 GetAvailablePoses 导致崩溃,因为我可以在代码中的该位置之前返回并且它不会崩溃。 【参考方案1】:

玩够了,我终于弄明白了。 GetAllAvailablePoses 确实希望您预先分配每个 char* 的空间并告诉它要填充的空间有多大:

# foo.pxd

XnStatus GetAllAvailablePoses(XnChar **, XnUInt32, XnUInt32&)

# foo.pyx

def get_available_poses(self):
    cdef: 
        int i 
        bytes name 
        XnStatus stat
        XnUInt32 nameLength = 256
        XnUInt32 size = self.handle.GetNumberOfPoses()

    cdef XnChar **buf = <XnChar**>malloc(size * sizeof(XnChar*))

    if not buf:
        raise MemoryError()

    for i in range(size):
        buf[i] = <XnChar*>malloc(nameLength)

    out = [None]*size

    try:
        stat = self.handle.GetAllAvailablePoses(buf, nameLength, size)

        for i in range(size):
            name = <char*>buf[i]
            out[i] = name

    finally:
        free(buf)

    return out

# Out: ['Psi', 'CrossHandsPose', 'Wave', 'Click', 'RaiseHand', 'MovingHand']

【讨论】:

通过查看您提供的链接......我认为您想针对该功能提交文档错误。它非常清楚地表示指向预分配内存的指针,而不是指向预分配内存指针数组的指针。而且,没有例子,你的困惑正是他们应该预料到的。无论如何,很高兴你解决了。 @abarnert:感谢您的支持!对于几乎没有 C++ 经验的尝试学习 cython 的人来说,这可能更令人困惑。尽管您对他们要求nameLength 值说明为每个char* 分配了多少内存这一事实有何看法?在我看来,他们想要的只是填充 char 数据的空间。 是的,我想这似乎是合理的。对 C++ 语言和 C 风格 API 有足够经验的人很可能通过第二次或第三次猜测就得到了正确的答案……但仍然不是文档所显示的理想。我敢肯定你不是唯一一个对此感到困惑的人。

以上是关于包装采用 char** [in/out] 的 C 函数调用,以在 cython 中返回 python 列表的主要内容,如果未能解决你的问题,请参考以下文章

包装类与自动拆装箱

程序的功能为接受用户输入的字符串,将大小写英文字母串均转义为小写字母并输出。 编译时有很多错误

SWIG 将 const unsigned char example[] 包装到 Java byte[] 作为参数

SWIG:numpy 包装器的意外结果?

如何将 Swift 字符串数组传递给采用 char ** 参数的 C 函数

C++ COM [in, out] 安全数组