通过readinto()将二进制数据解析为ctypes结构对象

2024-04-29 11:18:03 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试处理二进制格式,如下所示:

http://dabeaz.blogspot.jp/2009/08/python-binary-io-handling.html

>>> from ctypes import *
>>> class Point(Structure):
>>>     _fields_ = [ ('x',c_double), ('y',c_double), ('z',c_double) ]
>>>
>>> g = open("foo","rb") # point structure data
>>> q = Point()
>>> g.readinto(q)
24
>>> q.x
2.0

我已经定义了头的结构,并试图将数据读入我的结构,但我遇到了一些困难。 我的结构是这样的:

class BinaryHeader(BigEndianStructure):
    _fields_ = [
                ("sequence_number_4bytes", c_uint),
                ("ascii_text_32bytes", c_char),
                ("timestamp_4bytes", c_uint),
                ("more_funky_numbers_7bytes", c_uint, 56),
                ("some_flags_1byte", c_byte),
                ("other_flags_1byte", c_byte),
                ("payload_length_2bytes", c_ushort),

                ] 

ctypes documentation说:

For integer type fields like c_int, a third optional item can be given. It must be a small positive integer defining the bit width of the field.

因此,对于("more_funky_numbers_7bytes", c_uint, 56),我试图将该字段定义为7字节字段,但得到的错误是:

ValueError: number of bits invalid for bit field

所以我的第一个问题是,如何定义一个7字节的int字段?

然后,如果我跳过这个问题并注释掉“更多有趣的数字”字段,得到的数据将加载到。。但正如预期的那样,只有1个字符被加载到“ascii_32bytes”中。出于某种原因,返回16,我假设是它读入结构的计算字节数。。。但是如果我在注释我的“funky number”字段,而“ascii_text_32bytes”只给出一个字符(1个字节),那不应该是13,而不是16???

然后我尝试将char字段分解成一个单独的结构,并在头结构中引用它。但那也不管用。。。

class StupidStaticCharField(BigEndianStructure):
    _fields_ = [
                ("ascii_text_1", c_byte),
                ("ascii_text_2", c_byte),
                ("ascii_text_3", c_byte),
                ("ascii_text_4", c_byte),
                ("ascii_text_5", c_byte),
                ("ascii_text_6", c_byte),
                ("ascii_text_7", c_byte),
                ("ascii_text_8", c_byte),
                ("ascii_text_9", c_byte),
                ("ascii_text_10", c_byte),
                ("ascii_text_11", c_byte),
                .
                .
                .
                ]

class BinaryHeader(BigEndianStructure):
    _fields_ = [
                ("sequence_number_4bytes", c_uint),
                ("ascii_text_32bytes", StupidStaticCharField),
                ("timestamp_4bytes", c_uint),
                #("more_funky_numbers_7bytes", c_uint, 56),
                ("some_flags_1byte", c_ushort),
                ("other_flags_1byte", c_ushort),
                ("payload_length_2bytes", c_ushort),

                ] 

所以,任何关于如何:

  1. 定义一个7字节的字段(我需要使用定义的函数对其进行解码)
  2. 定义32字节的静态字符字段

更新

我发现了一个结构,似乎工作。。。

class BinaryHeader(BigEndianStructure):
    _fields_ = [
                ("sequence_number_4bytes", c_uint),
                ("ascii_text_32bytes", c_char * 32),
                ("timestamp_4bytes", c_uint),
                ("more_funky_numbers_7bytes", c_byte * 7),
                ("some_flags_1byte", c_byte),
                ("other_flags_1byte", c_byte),
                ("payload_length_2bytes", c_ushort),

                ]  

然而,现在我剩下的问题是,为什么在使用^{}时:

f = open(binaryfile, "rb")

mystruct = BinaryHeader()
f.readinto(mystruct)

它返回的是52,而不是预期的51。那个额外的字节从哪里来,又到哪里去了?

更新2 对于那些感兴趣的人,这里有一个example的替代struct方法来将值读入eryksun提到的namedtuple:

>>> record = 'raymond   \x32\x12\x08\x01\x08'
>>> name, serialnum, school, gradelevel = unpack('<10sHHb', record)

>>> from collections import namedtuple
>>> Student = namedtuple('Student', 'name serialnum school gradelevel')
>>> Student._make(unpack('<10sHHb', record))
Student(name='raymond   ', serialnum=4658, school=264, gradelevel=8)

Tags: textnumberfields字节定义asciibyte结构
1条回答
网友
1楼 · 发布于 2024-04-29 11:18:03

此行定义实际上用于定义bitfield

...
("more_funky_numbers_7bytes", c_uint, 56),
...

这是不对的。位字段的大小应小于或等于类型的大小,因此c_uint最多应为32,一个额外的位将引发异常:

ValueError: number of bits invalid for bit field

使用位字段的示例:

from ctypes import *

class MyStructure(Structure):
    _fields_ = [
        # c_uint8 is 8 bits length
        ('a', c_uint8, 4), # first 4 bits of `a`
        ('b', c_uint8, 2), # next 2 bits of `a`
        ('c', c_uint8, 2), # next 2 bits of `a`
        ('d', c_uint8, 2), # since we are beyond the size of `a`
                           # new byte will be create and `d` will
                           # have the first two bits
    ]

mystruct = MyStructure()

mystruct.a = 0b0000
mystruct.b = 0b11
mystruct.c = 0b00
mystruct.d = 0b11

v = c_uint16()

# copy `mystruct` into `v`, I use Windows
cdll.msvcrt.memcpy(byref(v), byref(mystruct), sizeof(v))

print sizeof(mystruct) # 2 bytes, so 6 bits are left floating, you may
                       # want to memset with zeros
print bin(v.value)     # 0b1100110000

你需要的是7个字节,所以你最终所做的是正确的:

...
("more_funky_numbers_7bytes", c_byte * 7),
...

至于结构的大小,它将是52,32位处理器上的4个字节上的额外字节将填充到align the structure,64位处理器上的8个字节。这里:

from ctypes import *

class BinaryHeader(BigEndianStructure):
    _fields_ = [
        ("sequence_number_4bytes", c_uint),
        ("ascii_text_32bytes", c_char * 32),
        ("timestamp_4bytes", c_uint),
        ("more_funky_numbers_7bytes", c_byte * 7),
        ("some_flags_1byte", c_byte),
        ("other_flags_1byte", c_byte),
        ("payload_length_2bytes", c_ushort),
    ]

mystruct = BinaryHeader(
    0x11111111,
    '\x22' * 32,
    0x33333333,
    (c_byte * 7)(*([0x44] * 7)),
    0x55,
    0x66,
    0x7777
)

print sizeof(mystruct)

with open('data.txt', 'wb') as f:
    f.write(mystruct)

文件中的多余字节填充在other_flags_1bytepayload_length_2bytes之间:

00000000 11 11 11 11 ....
00000004 22 22 22 22 """"
00000008 22 22 22 22 """"
0000000C 22 22 22 22 """"
00000010 22 22 22 22 """"
00000014 22 22 22 22 """"
00000018 22 22 22 22 """"
0000001C 22 22 22 22 """"
00000020 22 22 22 22 """"
00000024 33 33 33 33 3333
00000028 44 44 44 44 DDDD
0000002C 44 44 44 55 DDDU
00000030 66 00 77 77 f.ww
            ^
         extra byte

当涉及到文件格式和网络协议时,这是一个问题。若要将其更改为1,请将其打包:

 ...
class BinaryHeader(BigEndianStructure):
    _pack_ = 1
    _fields_ = [
        ("sequence_number_4bytes", c_uint),
...

文件将是:

00000000 11 11 11 11 ....
00000004 22 22 22 22 """"
00000008 22 22 22 22 """"
0000000C 22 22 22 22 """"
00000010 22 22 22 22 """"
00000014 22 22 22 22 """"
00000018 22 22 22 22 """"
0000001C 22 22 22 22 """"
00000020 22 22 22 22 """"
00000024 33 33 33 33 3333
00000028 44 44 44 44 DDDD
0000002C 44 44 44 55 DDDU
00000030 66 77 77    fww 

至于struct,在你的情况下,这不会使事情变得更容易。遗憾的是,它不支持格式中的嵌套元组。例如:

>>> from struct import *
>>>
>>> data = '\x11\x11\x11\x11\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22
\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x33
\x33\x33\x33\x44\x44\x44\x44\x44\x44\x44\x55\x66\x77\x77'
>>>
>>> BinaryHeader = Struct('>I32cI7BBBH')
>>>
>>> BinaryHeader.unpack(data)
(286331153, '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"'
, '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"'
, '"', '"', 858993459, 68, 68, 68, 68, 68, 68, 68, 85, 102, 30583)
>>>

此结果不能使用namedtuple,您仍然可以根据索引对其进行解析。如果你能做一些像'>I(32c)(I)(7B)(B)(B)H'这样的事情,它就会起作用。自2003年以来,(Extend struct.unpack to produce nested tuples)已在此处请求此功能,但此后未执行任何操作。

相关问题 更多 >