1

不确定 Python 3.3.2 总是将结构打包到一个新的结构单元中是否正常。下面的代码演示了这个问题。

sts结构只占8位。pkt 在状态之前只有 24 位字段,因此 pkt 的大小应该是 32 位,而不是 64 位。pkt 的打印清楚地表明 Python 将 sts 打包到一个新的 32 位整数中,并留下 8 位未使用的空间以及另一个 32 位整数中的所有其他 pckt 字段

import ctypes
class sts( ctypes.BigEndianStructure ):
    _fields_ = [( "valid", ctypes.c_uint8, 1 ),
                ( "inout", ctypes.c_uint8, 2 ),
                ( "exception", ctypes.c_uint8, 2 ),
                ( "error", ctypes.c_uint8, 3 ), ]

class pkt( ctypes.BigEndianStructure ):
    _fields_ = [( "uid", ctypes.c_uint32, 8 ),
                ( "sid", ctypes.c_uint32, 16 ),
                ( "sts", sts, ), ]

print("sts {:d}-byte".format(ctypes.sizeof(sts)))
print("pkt {:d}-byte".format(ctypes.sizeof(pkt)))
a=pkt(0xFF,0xDEAD,(0x1,0x3,0x3,0x7))
print("uid {:02X}".format(a.uid))
print("sid {:02X}".format(a.sid))
print("sts {:02X}".format(ctypes.string_at(ctypes.addressof(a.sts))[0]))
for b in ctypes.string_at(ctypes.addressof(a),8):
    print("{:02X}".format(b))

另一个代码来帮助解释问题。这段代码的输出表明 Python 确实以紧凑的形式打包字段,但具有结构的字段总是从一个新单元开始。

import ctypes

class sts( ctypes.BigEndianStructure ):
    _fields_ = [( "valid", ctypes.c_uint8, 1 ),
                ( "inout", ctypes.c_uint8, 2 ),
                ( "exception", ctypes.c_uint8, 2 ),
                ( "error", ctypes.c_uint8, 3 ), ]

class pkt( ctypes.BigEndianStructure ):
    _fields_ = [( "uid", ctypes.c_uint32, 8 ),
                ( "sid", ctypes.c_uint32, 16 ),
                ( "sts", sts ),
                ( "sts1", sts ),
                ( "gid", ctypes.c_uint16 ), ]

print("sts {:d}-byte".format(ctypes.sizeof(sts)))
print("pkt {:d}-byte".format(ctypes.sizeof(pkt)))
a=pkt(0xFF,0xDEAD,(0x1,0x3,0x3,0x7),(0x1,0x2,0x3,0x7),0xBEEFABCD)
print("uid {:02X}".format(a.uid))
print("sid {:02X}".format(a.sid))
print("sts {:02X}".format(ctypes.string_at(ctypes.addressof(a.sts))[0]))
for b in ctypes.string_at(ctypes.addressof(a),8):
    print("{:02X}".format(b))
4

2 回答 2

2

使用 pack 和 compact 字段解决了这个问题。仅在 Windows 上尝试过

import ctypes
class sts( ctypes.BigEndianStructure ):
    _pack_ = 1
    _fields_ = [( "valid", ctypes.c_uint8, 1 ),
                ( "inout", ctypes.c_uint8, 2 ),
                ( "exception", ctypes.c_uint8, 2 ),
                ( "error", ctypes.c_uint8, 3 ), ]

class pkt( ctypes.BigEndianStructure ):
    _pack_ = 1
    _fields_ = [( "uid", ctypes.c_uint8 ),
                ( "sid", ctypes.c_uint16 ),
                ( "sts", sts, ), ]

print("sts {:d}-byte".format(ctypes.sizeof(sts)))
print("pkt {:d}-byte".format(ctypes.sizeof(pkt)))
a=pkt(0xFF,0xDEAD,(0x1,0x3,0x3,0x7))
print("uid {:02X}".format(a.uid))
print("sid {:02X}".format(a.sid))
print("sts {:02X}".format(ctypes.string_at(ctypes.addressof(a.sts))[0]))
for b in ctypes.string_at(ctypes.addressof(a),8):
    print("{:02X}".format(b))
于 2013-07-22T17:50:29.200 回答
1

Microsoft 的编译器(或带有 -mms-bitfields 的 gcc)不会为不同的整数类型共享一个存储单元。但是,在 Linux 上使用 gcc,pkt确实只使用 4 个字节。ctypes 遵循平台的约定,但您只能对位字段使用整数类型。例如,您不能使用("sts", sts, 8). 如果您的编译器pkt以 4 个字节存储,则必须修改 ctypes 定义以获得相同的大小。最简单的选项是内联sts定义中的字段。使用 aUnion也可以。

编辑:

由于构造函数的编写方式,您必须显式设置元组中的位,这只能与整数类型一起使用。它不会查看先前定义的结构的内容,以查看它是否可用于扩展开放位域。在您的示例中,查看reprofpkt.uidpkt.sidofs是存储单元中的字节偏移量和位偏移量。另一方面,因为pkt.sts的值只有 4 个字节——没有设置和宏ofs使用的位域信息。GETBITFIELDSET

以下是源链接:

由于 gcc(Windows 上除外)自动尝试打包数据以填充位字段存储单元(但在 1 字节边界上,即它不会将 28 位字段与另一个定义的 4 位字段结合起来) struct),请注意在使用位域时仔细检查布局。要调整 ctypes 定义,您可以内联字段、显式设置整数类型的位数或使用联合。


仅供参考,在编写此编辑时,我遇到了一个错误。在非 Windows 平台上,PyCField_FromDesc即使您切换存储大小,也会继续一个位字段。例如,这就是 gcc 在 Linux 上的工作方式。但是我从来没有真正使用过它,这实际上是我第一次费心去看GETBITFIELD宏。如果位字段切换到较小的存储大小,它会使用较小大小的 getter 方法,并结合整个上下文中字段的大小信息(位偏移和位数)。这通常行不通,因为宏最终会向“左”位移一个负数,这实际上是一个右移,将所有数据移出,留下零。例如:

from ctypes import *

class Test(Structure):
    _fields_ = [
        ('x', c_uint32, 16),
        ('y', c_uint8, 4),
    ]

>>> t = Test.from_buffer_copy(b'\xAA\xAA\xAA\xAA')
>>> bytearray(t)
bytearray(b'\xaa\xaa\xaa\xaa')
>>> hex(t.x)
'0xaaaa'
>>> t.y
0
于 2013-06-18T09:33:16.047 回答