问题 在读/写二进制数据结构时访问位域


我正在为二进制格式编写解析器。这种二进制格式涉及不同的表,这些表再次是二进制格式,通常包含不同的字段大小(大约在50到100之间)。

这些结构中的大多数将具有位域,并且在C中表示时将看起来像这些:

struct myHeader
{
  unsigned char fieldA : 3
  unsigned char fieldB : 2;
  unsigned char fieldC : 3;
  unsigned short fieldD : 14;
  unsigned char fieldE : 4
}

我遇到了struct模块,但意识到它的最低分辨率是一个字节而不是一点,否则该模块几乎适合这项工作。

我知道使用ctypes支持位域,但我不知道如何在这里连接包含位域的ctypes结构。

我的另一个选择是自己操作这些位并将其提供给字节并将其与struct模块一起使用 - 但由于我有接近50-100种不同类型的此类结构,因此编写代码变得更容易出错。我也担心效率,因为这个工具可能用于解析大千兆字节的二进制数据。

谢谢。


6846
2017-08-25 23:29


起源

还有第三方位数组/位操作库。 - agf
这将是一项相当多的工作,但你可以设计一个类,它可以解析C风格的结构定义(或类似于消除包装歧义的东西)到每个位域的一组掩码,通过数据读取数据struct模块到达字节级别,并提供 __getattr__ 访问。 - Russell Borogove
是的我现在遇到了这些工具 - 蟒蛇,比特串, 构造, BitReader  - 阅读他们的文档。 Bit Reader似乎是一个可行的解决方案,但我明白了 这里 表现将会受到重创。从我们的基本文档中找到的构造不支持位字段。 Python-bitstring听起来很有希望,需要深入挖掘 - Tuxdude
是的Russell是我现在的最后一个选择 - 类似于使用struct模块支持位域的更高级抽象。 - Tuxdude


答案:


运用 比特串 (你提到你正在看)它应该很容易实现。首先创建一些要解码的数据:

>>> myheader = "3, 2, 3, 14, 4"
>>> a = bitstring.pack(myheader, 1, 0, 5, 1000, 2)
>>> a.bin
'00100101000011111010000010'
>>> a.tobytes()
'%\x0f\xa0\x80'

然后再解码它就是

>>> a.readlist(myheader)
[1, 0, 5, 1000, 2]

你主要担心的可能是速度。该库是经过优化的Python,但这并不像C库那么快。


4
2017-08-26 10:02



谢谢Scott - 是的,我已经检查了你的bitstring库,它确实非常接近我的要求。事实上,我在邮件列表中发布了这个问题 这里。我可以理解它可以作为列表阅读 - 但我想最好使用字典只是为了方便代码可读性,因为我要处理的结构将容易超过20或30个字段。我知道它在pack中受支持,但想知道如何在unpack中使用它,因为这将是主要功能。 - Tuxdude
@Ash:你还不能解压缩到字典。我认为你需要像提出的解码方法 这里,部分因为我真正想要返回的是一个有序的字典 - 我不确定无序字典是否有用。我会考虑更多...... - Scott Griffiths
是的,返回一个有序的字典是有意义的,但我想它的支持仅存在于Python 3.3a0中(或至少基于页面所说的内容) 这里 - PEP372 - Tuxdude


我没有严格测试过这个,但它似乎适用于无符号类型(编辑:它也适用于有符号字节/短类型)。

编辑2:这真的很受欢迎。这取决于库的编译器将这些位打包到结构中的方式,这不是标准化的。例如,使用gcc 4.5.3只要我不使用该属性来打包结构就可以工作,即 __attribute__ ((__packed__)) (所以它不是6个字节,而是打包成4个字节,你可以检查它 __alignof__ 和 sizeof)。我可以通过添加来使它几乎工作 _pack_ = True 到ctypes结构定义,但是对于fieldE失败了。 gcc注意到:“GCC 4.4中”填充位字段'fieldE'的偏移已经改变。

import ctypes

class MyHeader(ctypes.Structure):
    _fields_ = [
        ('fieldA', ctypes.c_ubyte, 3),
        ('fieldB', ctypes.c_ubyte, 2),
        ('fieldC', ctypes.c_ubyte, 3),
        ('fieldD', ctypes.c_ushort, 14),
        ('fieldE', ctypes.c_ubyte, 4),
    ]

lib = ctypes.cdll.LoadLibrary('C/bitfield.dll')

hdr = MyHeader()
lib.set_header(ctypes.byref(hdr))

for x in hdr._fields_:
    print("%s: %d" % (x[0], getattr(hdr, x[0])))

输出:

fieldA: 3
fieldB: 1
fieldC: 5
fieldD: 12345
fieldE: 9

C:

typedef struct _MyHeader {
    unsigned char  fieldA  :  3;
    unsigned char  fieldB  :  2;
    unsigned char  fieldC  :  3;
    unsigned short fieldD  : 14;
    unsigned char  fieldE  :  4;
} MyHeader, *pMyHeader; 

int set_header(pMyHeader hdr) {

    hdr->fieldA = 3;
    hdr->fieldB = 1;
    hdr->fieldC = 5;
    hdr->fieldD = 12345;
    hdr->fieldE = 9;

    return(0);
}

6
2017-08-26 03:22



查看经过测试的示例,无需任何C代码或dll Python有比特字段类型吗? - nealmcb
@nealmcb - 您的示例表示在Python本身中存储此类数据的方法。但是,如何从/向可以读/写到磁盘的字节流导入或导出这些数据,或者可以通过网络进行recvd /发送? - Tuxdude
@ash这就是工会的用武之地 flags.asbyte 该示例中的字段。谢谢你指出它不是那么清楚。我在那里打磨了文字,使其更加清晰。嘿:) - nealmcb