当字段和结构适合时,Python Ctypes不紧凑结构

时间:2013-06-17 23:40:45

标签: python-3.x ctypes

不确定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))

2 个答案:

答案 0 :(得分: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))

答案 1 :(得分:1)

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

编辑:

由于构造函数的编写方式,您必须显式设置元组中的位,这些位只能用于整数类型。它不会查看先前定义的结构的内容,以查看它是否可用于扩展打开的位域。在您的示例中,请查看reprpkt.uid的{​​{1}}。 pkt.sid是存储单元中的字节偏移量和位偏移量。另一方面,对于ofspkt.sts的值仅为4个字节 - 没有设置将由ofsGETBITFIELD宏使用的位字段信息

以下是源链接:

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


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

GETBITFIELD