如何获取带可变大小缓冲区的C类型结构体字段值及优化实现
问题描述
现有以下Python代码,使用ctypes解析串口读取的二进制数据:
import readboard import struct import ctypes as c def readbuffer(data): size= struct.unpack('<h', data[64:66])[0] class tabledata(c.Structure): _fields_ = [('delta', c.c_float), ('x', c.c_float)] class tableparameters(c.Structure): _fields_ = [('parameter1', c.c_uint16*30), ('parameter2', c.c_float), ('size', c.c_uint16), ('parameter3', c.c_uint16), ('parameter4', c.c_float), ('parameter5', c.c_float), ('Table', tabledata*size)] return tableparameters board = readboard.comport('COM1') data = board.custommsg(readcommand, 256) a = readbuffer(data) print(a.parameter3) print(a.parameter4) print(a.Table) print(a)
执行后输出仅显示字段的类型、偏移量和大小,无法获取实际值:
<Field type=c_ushort, ofs=66, size=2> <Field type=c_float, ofs=68, size=4> <Field type=tabledata, ofs=76, size=16> <class '__main__.readbuffer.<locals>.tableparameters'>
需解决两个问题:
- 如何获取字段的实际值?
- 当前通过
struct.unpack提前解析size字段定义可变大小的Table结构体,有没有更优的实现方案?
解决方案
一、获取字段实际值的问题
你的代码核心错误是readbuffer返回了结构体类而非解析后的实例,只需将二进制数据转换成结构体实例即可拿到实际值:
def readbuffer(data): size= struct.unpack('<h', data[64:66])[0] class tabledata(c.Structure): _fields_ = [('delta', c.c_float), ('x', c.c_float)] class tableparameters(c.Structure): _fields_ = [('parameter1', c.c_uint16*30), ('parameter2', c.c_float), ('size', c.c_uint16), ('parameter3', c.c_uint16), ('parameter4', c.c_float), ('parameter5', c.c_float), ('Table', tabledata*size)] # 将二进制数据转为结构体实例并返回 buffer = c.create_string_buffer(data) struct_ptr = c.cast(buffer, c.POINTER(tableparameters)) return struct_ptr.contents # 调用后直接访问实例字段即可拿到实际值 a = readbuffer(data) print(a.parameter3) # 输出实际数值 print(a.parameter4) # 输出实际浮点数 # 遍历Table数组 for item in a.Table: print(f"delta: {item.delta}, x: {item.x}")
二、可变大小结构体的更优实现方案
提前解析size的思路可行,这里提供两种更简洁的优化方案:
方案1:拆分固定/可变部分解析
先解析结构体的固定字段拿到size,再动态创建包含可变数组的完整结构体:
def readbuffer(data): # 定义仅包含固定字段的基础结构体 class tableparameters_base(c.Structure): _fields_ = [('parameter1', c.c_uint16*30), ('parameter2', c.c_float), ('size', c.c_uint16), ('parameter3', c.c_uint16), ('parameter4', c.c_float), ('parameter5', c.c_float)] # 解析基础部分获取size base_ptr = c.cast(c.create_string_buffer(data), c.POINTER(tableparameters_base)) base_data = base_ptr.contents size = base_data.size # 定义可变数组的元素结构体 class tabledata(c.Structure): _fields_ = [('delta', c.c_float), ('x', c.c_float)] # 定义包含可变数组的完整结构体 class tableparameters(c.Structure): _fields_ = [('fixed', tableparameters_base), ('Table', tabledata*size)] # 解析完整结构体并返回 full_ptr = c.cast(c.create_string_buffer(data), c.POINTER(tableparameters)) return full_ptr.contents
方案2:模拟C语言柔性数组
用0长度数组模拟C语言的柔性数组,无需重复定义结构体:
def readbuffer(data): class tabledata(c.Structure): _fields_ = [('delta', c.c_float), ('x', c.c_float)] # 定义带柔性数组的结构体(Table字段用0长度数组占位) class tableparameters(c.Structure): _fields_ = [('parameter1', c.c_uint16*30), ('parameter2', c.c_float), ('size', c.c_uint16), ('parameter3', c.c_uint16), ('parameter4', c.c_float), ('parameter5', c.c_float), ('Table', tabledata*0)] # 解析基础结构体拿到size base_struct = c.cast(c.create_string_buffer(data), c.POINTER(tableparameters)).contents size = base_struct.size # 计算可变数组的内存偏移,手动获取数组指针并赋值 table_offset = c.sizeof(tableparameters) table_ptr = c.cast(c.addressof(base_struct) + table_offset, c.POINTER(tabledata*size)) base_struct.Table = table_ptr.contents return base_struct
内容的提问来源于stack exchange,提问作者Isaac Zaiek
相关产品推荐
相关产品推荐

