【问题标题】:data gets corrupted between c and python数据在 c 和 python 之间被破坏
【发布时间】:2019-04-15 13:38:40
【问题描述】:

我正在尝试使用 Cython 和 ctypes 使用 Python 调用 c 库函数。 但是数据字节以某种方式损坏。有人可以帮忙找出问题吗?

testCRC.c:

#include <stdio.h>
unsigned char GetCalculatedCrc(const unsigned char* stream){
   printf("Stream is %x %x %x %x %x %x  %x\n",stream[0],stream[1],stream[2],stream[3],stream[4],stream[5],stream[6]);
   unsigned char dummy=0;
   return dummy;  
}

wrapped.pyx:

# Exposes a c function to python
def c_GetCalculatedCrc(const unsigned char*  stream):
     return GetCalculatedCrc(stream)

test.py:

  x_ba=(ctypes.c_ubyte *7)(*[0xD3,0xFF,0xF7,0x7F,0x00,0x00,0x41]) 
  x_ca=(ctypes.c_char * len(x_ba)).from_buffer(x_ba)
  y=c_GetCalculatedCrc(x_ca.value)

输出:

流是 d3 ff f7 7f 0 0 5f # 预期 0xD3,0xFF,0xF7,0x7F,0x00,0x00,0x41

解决方案:

1。 我必须将 cython 更新到 0.29 才能修复不允许使用类型化内存的错误。(只读问题)。

2。 它通过 x_ca.raw 工作。但是当 x_ca.value 被传递时,它会抛出错误“超出范围访问”。

根据@ead 和@DavidW 的建议:

'.pyx':

def c_GetCalculatedCrc(const unsigned char[:]  stream):
    # Exposes a c function to python
    print "received %s\n" %stream[6]
    return GetCalculatedCrc(&stream[0])

'test.py':

x_ba=(ctypes.c_ubyte *8)(*[0x47,0xD3,0xFF,0xF7,0x7F,0x00,0x00,0x41])
x_ca=(ctypes.c_char * len(x_ba)).from_buffer(x_ba)
y=c_GetCalculatedCrc(x_ca.raw)

输出:

流是 47 d3 ff f7 7f 0 0 41

【问题讨论】:

  • 问题出在 ctypes 方面 - 试试print(len(x_ca.value))

标签: python cython ctypes


【解决方案1】:

正如@DavidW 所指出的,问题在于您对x_ca.value 的使用:当x_ca.value 被调用时,每次创建一个新的字节对象(参见documentation)并复制内存:

x_ca.value is x_ca.value
#False -> every time a new object is created

但是,当内存被复制时,它会将\0-character 处理为字符串的结尾(这对于C-strings 很典型),如source code 所示:

static PyObject *
CharArray_get_value(CDataObject *self, void *Py_UNUSED(ignored))
{
    Py_ssize_t i;
    char *ptr = self->b_ptr;
    for (i = 0; i < self->b_size; ++i)
        if (*ptr++ == '\0')
            break;
    return PyBytes_FromStringAndSize(self->b_ptr, i);
}

因此x_ca.value 的结果是一个长度为4 的字节对象,它x_ca 共享内存 - 当您访问stream[6] 时,它会导致未定义的行为 -任何事情都可能发生(也是崩溃)。


那么可以做些什么呢?

通常,您不能在 def 函数中使用指针参数,但 char * 是一个例外 - bytes 对象可以自动转换为 char *,但这不会通过缓冲协议,但通过PyBytes_AsStringAndSize

这就是为什么你不能将x_ca 传递给c_GetCalculatedCrc 的原因:x_ca 实现了缓冲区协议,但不是bytes-object,因此没有PyBytes_AsStringAndSize

另一种方法是使用类型化内存视图,它利用缓冲区协议,即

%%cython 
def c_GetCalculatedCrc(const unsigned char[:] stream):
    print(stream[6]);

现在直接传递x_ca,原始长度/内容:

c_GetCalculatedCrc(x_ca)
# 65    as expected

另一种选择是将x_ca.raw 传递给期望const unsigned char * 作为参数的函数,正如@DavidW 在cmets 中指出的那样,它与x_ca 共享内存。不过我更喜欢类型化的内存视图——它们比原始指针更安全,而且你不会遇到令人惊讶的未定义行为。

【讨论】:

  • 我认为您的缓冲区方法更好,但看起来x_ca.raw 将获得完整的字符串。当知道正确的大小时,.value 停在 '\0' 似乎是一个奇怪的选择
  • @DavidW 你是对的。他们在文档中的某处说:“可以使用原始属性访问(或更改)当前内存块内容;如果您想作为 NUL 终止字符串访问它,请使用 value 属性:”这样一来,就说得通了。但遗憾的是在value-property的描述中没有提到它。
  • 1.我必须将 cython 更新到 0.29 才能修复不允许使用类型化内存的错误。(只读问题)。 2. 它通过了 x_ca.raw。但是当 x_ca.value 被传递时,它会抛出错误“超出范围访问”。@DavidW ..谢谢
猜你喜欢
  • 2021-10-20
  • 2016-08-27
  • 2015-04-14
  • 1970-01-01
  • 2017-01-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多