来自缓冲区的 ctypes - Python

ctypes from buffer - Python

我正在使用 ctypes 从二进制数据缓冲区进行转换

log = DataFromBuffe.from_buffer(buffer)

在我的 class 我有

class DataFromBuffe(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
        ('id', ctypes.c_char * 1),
        ('name', ctypes.c_char * 30),
        ('value', ctypes.c_double),
        ('size', ctypes.c_uint16),
        ('date', type(datetime.datetime))
        ]

但是我有两个问题?

1 - 如何使用日期时间?字段 'date' 不工作。

2 - 字段 'size',由于某种原因是 BigEndian。是否可以仅针对此字段更改结构?

1 - How can I work with datetime? Fild 'date' is not working.

您的 date 字段 必须 ctypes 类型(或从 ctypes 类型继承的类型)。这意味着你必须找到一种方法将日期表示为数字(int、float、double,任何你想要的,但它不能是非 ctypes python 类型)。

在此示例中,我使用了众所周知的 Unix Epoch(可以用 ctypes.c_uint32 表示)

class DataFromBuffer(ctypes.LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('id', ctypes.c_char * 1),
        ('name', ctypes.c_char * 30),
        ('value', ctypes.c_double),
        ('size', ctypes.c_uint16),
        ('date', ctypes.c_uint32),  # date as a 32-bit unsigned int.
    ]

# snip

    now_date_time = datetime.datetime.now()
    now_int = int(now_date_time.timestamp())  # now as an integer (seconds from the unix epoch)
    print(f"Now - datetime: {now_date_time!s}; int: {now_int}")

    test_buffer = (b"A" + # id
        # snip
        now_int.to_bytes(4, "little")  # date
    )

至于日期时间的转换,我只是在结构中添加了一个函数成员,这样它就可以将日期 (a ctypes.c_uint32) 转换为日期时间:

    def date_to_datetime(self) -> datetime.datetime:
        """Get the date field as a python datetime.
        """
        return datetime.datetime.fromtimestamp(self.date)

2 - Field 'size', for some reason is BigEndian. Is it possible change structure just for this field?

不,这不可能。一种可能的方法是使用一个函数或 属性 来访问您想要的字段(在后台执行某种转换):

    def real_size(self) -> int:
        """Get the correct value for the size field (endianness conversion).
        """
        # note: there multiple way of doing this: bytearray.reverse() or struct.pack and unpack, etc.
        high = self.size & 0xff
        low = (self.size & 0xff00) >> 8
        return high | low

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import ctypes
import math
import datetime

class DataFromBuffer(ctypes.LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('id', ctypes.c_char * 1),
        ('name', ctypes.c_char * 30),
        ('value', ctypes.c_double),
        ('size', ctypes.c_uint16),
        ('date', ctypes.c_uint32),
    ]

    def date_to_datetime(self) -> datetime.datetime:
        """Get the date field as a python datetime.
        """
        return datetime.datetime.fromtimestamp(self.date)

    def real_size(self) -> int:
        """Get the correct value for the size field (endianness conversion).
        """
        # note: there multiple way of doing this: bytearray.reverse() or struct.pack and unpack, etc.
        high = self.size & 0xff
        low = (self.size & 0xff00) >> 8
        return high | low

if __name__ == '__main__':
    name = b"foobar"
    now_date_time = datetime.datetime.now()
    now_int = int(now_date_time.timestamp())  # now as an integer (seconds from the unix epoch)
    print(f"Now - datetime: {now_date_time!s}; int: {now_int}")

    test_buffer = (b"A" + # id
        name + (30 - len(name)) * b"\x00" +  # name (padded with needed \x00)
        bytes(ctypes.c_double(math.pi)) +  # PI as double
        len(name).to_bytes(2, "big") +  # size (let's pretend it's the name length)
        now_int.to_bytes(4, "little")  # date (unix epoch)
    )

    assert ctypes.sizeof(DataFromBuffer) == len(test_buffer)

    data = DataFromBuffer.from_buffer(bytearray(test_buffer))
    print(f"date: {data.date}; as datetime: {data.date_to_datetime()}")
    print(f"size: {data.size} ({data.size:#x}); real size: {data.real_size()} ({data.real_size():#x})")

输出:

Now - datetime: 2019-07-31 14:52:21.193023; int: 1564577541
date: 1564577541; as datetime: 2019-07-31 14:52:21
size: 1536 (0x600); real size: 6 (0x6)