2013-08-30 41 views
7

我試圖處理二進制格式,以下這裏的例子:解析二進制數據到ctypes的結構對象

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字段?

然後如果我跳過這個問題並註釋掉「more_funky_numbers_7bytes」字段,結果數據被載入..但是正如所期望的,只有1個字符被加載到「ascii_text_32bytes」中。並由於某種原因返回16,我假設它是計算的字節數讀取到結構中......但如果我註釋掉我的「時髦數字」字段和「」ascii_text_32bytes「只給一個字符(1字節) ,不應該說是13,不是16 ???

然後我試圖打破了煤焦領域成爲一個獨立的結構,和參考,從我的頭結構之內。但是,這不是工作要麼...

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字節字段(I'l升無需解碼使用定義的函數)
  2. 定義的32個字節

UPDATE

我發現,似乎工作結構的靜態字符場...

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), 

       ] 

但是現在,我剩下的問題是,爲什麼當使用.readinto()

f = open(binaryfile, "rb") 

mystruct = BinaryHeader() 
f.readinto(mystruct) 

它返回52而不是預期的,51。那個額外的字節來自哪裏,它到底在哪裏?

UPDATE 2 對於那些有興趣這裏是一個替代struct方法來讀取值到由eryksun提及namedtuple的example

>>> 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) 
+0

如果你用一些十六進制編輯器查看你的二進制文件,你看到51個字節嗎?另外,'len(mystruct)'說什麼? –

+0

是的,'binaryfile'超過50KB。 'len(mystruct)'似乎不起作用,但'sizeof(mystruct)'確實返回52 ... – monkut

+1

您可以將'_pack_ = 1'添加到定義中,但考慮使用'struct'模塊和'而不是「namedtuple」。 – eryksun

回答

5

此行定義實際上是用於定義bitfield

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

這是錯誤的。位域的尺寸應小於或等於所述類型的大小,所以c_uint應該是至多32,一個額外的位將提高異常:

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 

ValueError: number of bits invalid for bit field 

使用位字段的實施例

你需要的是7個字節所以你endup做的是正確的:

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

至於大小的結構,這將是52歲,我額外的字節填充至align the structure 32位處理器上的4個字節或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),但自此以後就沒有做任何事情。

+0

感謝您的詳細解釋!你也設法回答我沒有回答的問題:「我該如何處理每個4位的2個字段?」。所以ctypes方法進展順利。我試圖弄清楚如何處理4位的情況下,遇到了'struct'。 – monkut

+0

@monkut我相信'struct'不支持這個,所有它支持的是基本的數據類型。您必須使用[按位操作](http://en.wikipedia.org/wiki/Bitwise_operation)並手動執行。 – 2013-08-31 07:59:59

+0

再次感謝,我實施了它,比我以前的草率代碼大概提高了60%。唯一的問題是,當我去嘗試從tar文件讀取時......顯然ExFileObject(由tarinfo返回的文件對象)不支持'.readinto(b)',doh! – monkut