2014-10-09 102 views
1

我使用ctypes位字段来解剖紧密压缩的二进制数据。我将一条记录的数据作为一个字符串填充到一个联合中,然后将整个关键字段提取出来。Python ctypes - 当字符串嵌入null时设置c_char数组?

如果缓冲区中没有空值,但任何嵌入的空值都会导致cytpes截断字符串,则此功能非常有用。

实施例:

from ctypes import * 

class H(BigEndianStructure): 
    _fields_ = [ ('f1', c_int, 8), 
       ('f2', c_int, 8), 
       ('f3', c_int, 8), 
       ('f4', c_int, 2) 
       # ... 
       ] 

class U(Union): 
    _fields_ = [ ('fld', H), 
       ('buf', c_char * 6) 
       ] 

# With no nulls, works as expected... 
u1 = U() 
u1.buf='abcabc' 
print '{} {} {} (expect: 97 98 99)'.format(u1.fld.f1, u1.fld.f2, u1.fld.f3) 

# Embedded null breaks it... This prints '97 0 0', NOT '97 0 99' 
u2 = U() 
u2.buf='a\x00cabc' 
print '{} {} {} (expect: 97 0 99)'.format(u2.fld.f1, u2.fld.f2, u2.fld.f3) 

浏览ctypes的源,我看到两种方法设置一个字符数组,CharArray_set_value()和CharArray_set_raw()。看起来CharArray_set_raw()将正确处理空值,而CharArray_set_value()则不会。

但我无法弄清楚如何调用原始版本......它看起来像一个属性,所以我希望是这样的:

ui.buf.raw = 'abcabc' 

但收益率:

AttributeError: 'str' object has no attribute raw 

任何指导赞赏。 (包括完全不同的方法!)

(注意:我需要每秒处理数千条记录,所以效率非常关键,使用数组理解在结构体中填充字节数组,但速度要慢100倍。

回答

0

c_char*6不幸被作为一个nul结尾的字符串处理。切换到c_byte*6代替,但失去了与字符串初始化的方便:

from ctypes import * 

class H(BigEndianStructure): 
    _fields_ = [ ('f1', c_int, 8), 
       ('f2', c_int, 8), 
       ('f3', c_int, 8), 
       ('f4', c_int, 2) 
       # ... 
       ] 

class U(Union): 
    _fields_ = [ ('fld', H), 
       ('buf', c_byte * 6) 
       ] 

u1 = U() 
u1.buf=(c_byte*6)(97,98,99,97,98,99) 
print '{} {} {} (expect: 97 98 99)'.format(u1.fld.f1, u1.fld.f2, u1.fld.f3) 

u2 = U() 
u2.buf=(c_byte*6)(97,0,99,97,98,99) 
print '{} {} {} (expect: 97 0 99)'.format(u2.fld.f1, u2.fld.f2, u2.fld.f3) 

输出:

97 98 99 (expect: 97 98 99) 
97 0 99 (expect: 97 0 99) 
+0

谢谢,马克。这有效,但与将字符串中的字节整理到字节数组相关联的CPU开销使得我的应用程序的方法过于缓慢。 (我的试用比ctype的memcpy()慢100倍)。 – Simian 2014-10-13 17:20:15

0

您还可以创建您的结构/联合外的原始字符串数组:

mystring = (c_char * 6).from_buffer(u2) 
print mystring.raw 

这样你就没有任何转换开销。 我不知道为什么(c_char * 6)在单独使用时与使用在结构/联合中时表现不同?

+0

为了方便起见(通常),'c_char'和'c_wchar'数组的'CField'描述符是在'PyCField_FromDesc'中(在Modules/_ctypes/cfield.c中)专用的,用于使用's_get '/'s_set'和'U_get' /'U_set'。 – eryksun 2015-12-11 14:17:56

+1

不要为此使用'from_address',因为结果数组没有在源缓冲区'u2'上拥有引用。这是段错误灾难的秘诀。使用'(c_char * 6).from_buffer(u2)'。 – eryksun 2015-12-11 14:23:04

+0

在这种情况下,我更愿意将字段名称设置为私有(例如'_buf')并使用公共属性。 – eryksun 2015-12-11 14:24:48