【问题标题】:Convert ctypes Struct to bytearray in python在python中将ctypes Struct转换为bytearray
【发布时间】:2020-03-30 07:50:55
【问题描述】:

有没有办法转换包含指向字节数组的指针的 Ctypes 结构?

class SRamAccess(ctypes.Structure):
    _fields_ = [('channel', ctypes.c_uint), ('offset', ctypes.c_uint), ('len', ctypes.c_uint), ('data', ctypes.c_char_p)]

【问题讨论】:

  • 你也需要指针内容吗?

标签: python arrays ctypes


【解决方案1】:

只需将其传递给bytearray()

>>> import ctypes
>>> class SRamAccess(ctypes.Structure):
...  _fields_ = [('channel', ctypes.c_uint), ('offset', ctypes.c_uint), ('len', ctypes.c_uint), ('data', ctypes.c_char_p)]
...
>>> s = SRamAccess(1,2,3,b'blah')
>>> bytearray(s)
bytearray(b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\xf0 .\x1a\xed\x01\x00\x00')
#           \______________/\______________/\______________/\______________/\________________________/
#                   1               2               3         padding          pointer64 

请注意,'blah' 不是结构的一部分。最后 8 个字节(64 位 Python)是指针地址,之前的 4 个字节是填充,以将 8 字节指针与结构中的 8 字节偏移对齐。

您需要结构中的一个数组来查看数组内容:

>>> class SRamAccess(ctypes.Structure):
...  _fields_ = [('channel', ctypes.c_uint), ('offset', ctypes.c_uint), ('len', ctypes.c_uint), ('data', ctypes.c_char * 5)]
...
>>> s = SRamAccess(1,2,3,b'blah')
>>> bytearray(s)
bytearray(b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00blah\x00\x00\x00\x00')
#           \______________/\______________/\______________/\______/\__________/
#                   1               2               3         blah    padding

请注意,最后 3 个字节被填充以使整个结构的大小成为 4 个字节的倍数,因此此类结构的数组使 4 字节整数在 4 字节边界上对齐。

【讨论】:

    【解决方案2】:

    上市[Python 3.Docs]: ctypes - A foreign function library for Python

    一种方法是创建一个辅助方法来进行转换。下面是这样一个例子,它也进行了转换(我想只转换一种方式几乎没有用)。

    code00.py

    #!/usr/bin/env python
    
    import sys
    import ctypes as ct
    
    
    class SRamAccess(ct.Structure):
        _fields_ = [
            ("channel", ct.c_uint),
            ("offset", ct.c_uint),
            ("len", ct.c_uint),
            ("data", ct.c_char_p),
        ]
    
        @classmethod
        def deserialize(cls, buf):
            inst = cls.from_buffer(buf)
            return inst
    
        def serialize(self):
            struct_size = ct.sizeof(SRamAccess)
            size = struct_size + self.len
            buf = (ct.c_char * size)()
            ct.memmove(ct.addressof(buf), ct.addressof(self), struct_size)
            ct.memmove(ct.addressof(buf) + struct_size, self.data, self.len)
            return bytearray(buf)
    
        def __str__(self):
            s = self.__repr__()
            for field_name, _ in self._fields_[:-1]:
                s += "\n  {0:s}: {1:}".format(field_name, getattr(self, field_name))
            s += "\n  {0:s}:".format(self._fields_[-1][0])
            for i in range(self.len):
                s += " 0x{0:02X}".format(self.data[i])
            return "{0:s}\n".format(s)
    
    
    def main(*argv):
        text = b"abcd1234"
        ssrc = SRamAccess(1, 2, len(text), text)
        print("Src:", ssrc)
        buf = ssrc.serialize()
        print("Buf:", buf)
        sdst = SRamAccess.deserialize(buf)
        print("\nDst:", sdst)
    
    
    if __name__ == "__main__":
        print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        main(*sys.argv[1:])
        print("\nDone.")
    

    输出

    e:\Work\Dev\StackOverflow\q060926139>"e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code00.py
    Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32
    
    Src: <__main__.SRamAccess object at 0x00000298F25553C8>
      channel: 1
      offset: 2
      len: 8
      data: 0x61 0x62 0x63 0x64 0x31 0x32 0x33 0x34
    
    Buf: bytearray(b'\x01\x00\x00\x00\x02\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00 \xc6V\xf2\x98\x02\x00\x00abcd1234')
    
    Dst: <__main__.SRamAccess object at 0x00000298F2555448>
      channel: 1
      offset: 2
      len: 8
      data: 0x61 0x62 0x63 0x64 0x31 0x32 0x33 0x34
    
    
    Done.
    

    请注意,此表示还存储了不相关的最后一个字段(指针)地址(因为也存储了内容)。



    更新#0

    当有 NUL (0, \x00) char em>s 在字符串中 (text = b"abcd\x001234"),因为ctypes.c_char_p 用于NUL 终止的字符串。我认为 len 字段的存在自动暗示了上述情况。

    code01.py

    #!/usr/bin/env python
    
    import sys
    import ctypes as ct
    import struct
    
    
    CharPtr = ct.POINTER(ct.c_char)
    
    
    class SRamAccess(ct.Structure):
        _fields_ = [
            ("channel", ct.c_uint),
            ("offset", ct.c_uint),
            ("len", ct.c_uint),
            ("data", CharPtr),
        ]
    
        @classmethod
        def deserialize(cls, buf):
            fmt_prefix = "II"
            data_len = len(buf) - struct.calcsize(fmt_prefix)
            fmt = fmt_prefix + "b" * data_len
            unpacked = struct.unpack(fmt, buf)
            return cls(unpacked[0], unpacked[1], 0, unpacked[2:])
    
        def serialize(self):
            buf = struct.pack("II" + "b" * self.len, self.channel, self.offset, *self.data[:self.len])
            return bytearray(buf)
    
        def __setattr__(self, name, value):
            if name == "data":
                self.len = len(value)
                buf = (ct.c_char * len(value))(*value)
                super().__setattr__(name, buf)
            else:
                super().__setattr__(name, value)
    
        def __str__(self):
            s = self.__repr__()
            for field_name, _ in self._fields_[:-1]:
                s += "\n  {0:s}: {1:}".format(field_name, getattr(self, field_name))
            s += "\n  {0:s}:".format(self._fields_[-1][0])
            for i in range(self.len):
                s += " 0x{0:02X}".format(ord(self.data[i]))
            return "{0:s}\n".format(s)
    
    
    def main(*argv):
        text = b"abcd\x00123"
        ssrc = SRamAccess(1, 2, 12345, text)
        print("Src:", ssrc)
        buf = ssrc.serialize()
        print("Buf:", buf)
        sdst = SRamAccess.deserialize(buf)
        print("\nDst:", sdst)
    
    
    if __name__ == "__main__":
        print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        main(*sys.argv[1:])
        print("\nDone.")
    

    输出

    e:\Work\Dev\StackOverflow\q060926139>"e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code01.py
    Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32
    
    Src: <__main__.SRamAccess object at 0x0000025277C85348>
      channel: 1
      offset: 2
      len: 8
      data: 0x61 0x62 0x63 0x64 0x00 0x31 0x32 0x33
    
    Buf: bytearray(b'\x01\x00\x00\x00\x02\x00\x00\x00abcd\x00123')
    
    Dst: <__main__.SRamAccess object at 0x0000025277C85448>
      channel: 1
      offset: 2
      len: 8
      data: 0x61 0x62 0x63 0x64 0x00 0x31 0x32 0x33
    
    
    Done.
    

    注意事项(与上一版本的变化):

    【讨论】:

      猜你喜欢
      • 2011-05-15
      • 1970-01-01
      • 1970-01-01
      • 2020-12-30
      • 1970-01-01
      • 2010-10-28
      • 1970-01-01
      • 2015-04-01
      • 1970-01-01
      相关资源
      最近更新 更多