来自缓冲区的 ctypes - Python
Posted
技术标签:
【中文标题】来自缓冲区的 ctypes - Python【英文标题】:ctypes from buffer - Python 【发布时间】:2019-07-30 21:32:27 【问题描述】:我正在使用 ctypes 从二进制数据缓冲区转换
log = DataFromBuffe.from_buffer(buffer)
在我的课上
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 - 我如何使用日期时间?字段“日期”不起作用。
2 - 字段“大小”,出于某种原因是 BigEndian。是否可以仅针对该字段更改结构?
【问题讨论】:
【参考方案1】:1 - 我如何使用日期时间?字段“日期”不起作用。
您的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
)
至于转换为日期时间,我只是在结构中添加了一个函数成员,以便它可以将日期(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 - 字段“大小”,出于某种原因是 BigEndian。是否可以仅针对该字段更改结构?
不,这是不可能的。一种可能的方法是使用函数或属性来访问所需的字段(在后台执行某种转换):
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)
【讨论】:
以上是关于来自缓冲区的 ctypes - Python的主要内容,如果未能解决你的问题,请参考以下文章
使用 numpy/ctypes 公开 C 分配的内存缓冲区的更安全方法?