Конвертировать ctypes Struct в bytearray в python - PullRequest
0 голосов
/ 30 марта 2020

Есть ли способ преобразовать структуру Ctypes, включая указатель на байтовый массив?

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

Ответы [ 2 ]

1 голос
/ 30 марта 2020

Просто передайте его 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')

Обратите внимание, что «бла» не является частью структуры. Последние 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')

Обратите внимание, что последние 3 байта заполнены, чтобы вся структура была кратна размеру 4 байта, поэтому массив таких структур сохраняет 4 байта целые числа выровнены по 4-байтовым границам.

0 голосов
/ 30 марта 2020

Listing [Python 3.Docs]: ctypes - библиотека сторонних функций для 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 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.

Примечания (отличия от предыдущей версии):

  • Ручки NUL char s
  • Использует [Python 3.Docs]: struct - интерпретировать строки как упакованные двоичные данные для выполнения преобразований
  • len автоматически вычисляется из данных, переданных в качестве аргумента (в __ setattr __ ). Обратите внимание, что его последующая установка может привести к непредсказуемым результатам
    • Код более сложный
    • Буфер меньше (сериализованные данные не имеют значения)
...