如何在Python中使用ctypes调整可变大小缓冲区的尺寸?
问题:ctypes可变大小结构体修改尺寸无效的解决方法
问题代码
import struct import ctypes as ct def read_buffer(data): size = struct.unpack('<h', data[64:66])[0] class TableData(ct.Structure): _fields_ = [('delta', ct.c_float), ('x', ct.c_float)] def __repr__(self): return f'TableData(delta={self.delta}, x={self.x})' class TableParameters(ct.Structure): _fields_ = [('parameter1', ct.c_uint16 * 30), ('parameter2', ct.c_float), ('size', ct.c_uint16), ('parameter3', ct.c_uint16), ('parameter4', ct.c_float), ('parameter5', ct.c_float), ('Table', TableData * size)] def __repr__(self): return (f'TableParameters(parameter1={list(self.parameter1)},' f'parameter2={self.parameter2},' f'size={self.size},' f'parameter3={self.parameter3},' f'parameter4={self.parameter4},' f'parameter5={self.parameter5},' f'Table={list(self.Table)}') return TableParameters.from_buffer_copy(data) data = struct.pack('<30HfHHff4f',*range(30),1.125,2,8,2.5,3.25,4.75,5.5,6.25,7.75) a = read_buffer(data) print (a.parameter1[0]) print (a.parameter1[3]) print (a.parameter5) print ('------------') print (len(a.Table)) a.size = 10 print (len(a.Table))
运行输出
0 3 3.25 ------------ 2 2
问题分析
当前代码中,TableParameters结构体的Table字段类型是TableData * size,这里的size是函数调用时从数据中解析出的静态值,结构体类在定义时就固定了Table的数组大小。修改实例的size字段只是改变了结构体中的一个数值,并不会改变已定义的数组类型的内存布局和大小,因此len(a.Table)始终是定义时的初始值。
解决方案
方案1:动态重建结构体类(适合修改尺寸后重新加载数据的场景)
将结构体类的创建逻辑抽离成单独函数,当需要修改size时,根据新尺寸重新创建结构体类,再将原有数据复制到新实例中:
import struct import ctypes as ct def create_table_params_class(size): class TableData(ct.Structure): _fields_ = [('delta', ct.c_float), ('x', ct.c_float)] def __repr__(self): return f'TableData(delta={self.delta}, x={self.x})' class TableParameters(ct.Structure): _fields_ = [('parameter1', ct.c_uint16 * 30), ('parameter2', ct.c_float), ('size', ct.c_uint16), ('parameter3', ct.c_uint16), ('parameter4', ct.c_float), ('parameter5', ct.c_float), ('Table', TableData * size)] def __repr__(self): return (f'TableParameters(parameter1={list(self.parameter1)},' f'parameter2={self.parameter2},' f'size={self.size},' f'parameter3={self.parameter3},' f'parameter4={self.parameter4},' f'parameter5={self.parameter5},' f'Table={list(self.Table)}') return TableParameters def read_buffer(data): size = struct.unpack('<h', data[64:66])[0] TableParamsClass = create_table_params_class(size) return TableParamsClass.from_buffer_copy(data) data = struct.pack('<30HfHHff4f',*range(30),1.125,2,8,2.5,3.25,4.75,5.5,6.25,7.75) a = read_buffer(data) print(a.parameter1[0]) print(a.parameter1[3]) print(a.parameter5) print('------------') print(len(a.Table)) # 修改尺寸并重建实例 new_size = 10 NewTableParamsClass = create_table_params_class(new_size) new_a = NewTableParamsClass() # 复制原有固定字段数据 ct.memmove(ct.addressof(new_a), ct.addressof(a), ct.sizeof(a)) new_a.size = new_size # 初始化新的Table元素 for i in range(new_size): new_a.Table[i] = ct.cast((ct.c_float(0.1*i), ct.c_float(10+i)), ct.POINTER(TableData)).contents print(len(new_a.Table))
方案2:使用指针动态管理内存(适合动态增减元素的场景)
将Table字段改为指针类型,手动分配和释放内存,实现灵活调整数组大小:
import struct import ctypes as ct class TableData(ct.Structure): _fields_ = [('delta', ct.c_float), ('x', ct.c_float)] def __repr__(self): return f'TableData(delta={self.delta}, x={self.x})' class TableParameters(ct.Structure): _fields_ = [('parameter1', ct.c_uint16 * 30), ('parameter2', ct.c_float), ('size', ct.c_uint16), ('parameter3', ct.c_uint16), ('parameter4', ct.c_float), ('parameter5', ct.c_float), ('Table', ct.POINTER(TableData))] def __repr__(self): table_list = [self.Table[i] for i in range(self.size)] if self.Table else [] return (f'TableParameters(parameter1={list(self.parameter1)},' f'parameter2={self.parameter2},' f'size={self.size},' f'parameter3={self.parameter3},' f'parameter4={self.parameter4},' f'parameter5={self.parameter5},' f'Table={table_list}') def read_buffer(data): base_size = ct.sizeof(TableParameters) - ct.sizeof(ct.POINTER(TableData)) size = struct.unpack('<h', data[64:66])[0] params = TableParameters() # 复制固定部分数据 ct.memmove(ct.addressof(params), data, base_size) # 分配Table内存并复制数据 params.Table = ct.cast(ct.malloc(size * ct.sizeof(TableData)), ct.POINTER(TableData)) ct.memmove(params.Table, data[base_size:], size * ct.sizeof(TableData)) return params data = struct.pack('<30HfHHff4f',*range(30),1.125,2,8,2.5,3.25,4.75,5.5,6.25,7.75) a = read_buffer(data) print(a.parameter1[0]) print(a.parameter1[3]) print(a.parameter5) print('------------') print(a.size) # 修改尺寸并重新分配内存 new_size = 10 # 释放原有内存 ct.free(a.Table) # 分配新内存 a.Table = ct.cast(ct.malloc(new_size * ct.sizeof(TableData)), ct.POINTER(TableData)) a.size = new_size # 初始化新元素 for i in range(new_size): a.Table[i].delta = 0.1 * i a.Table[i].x = 10 + i print(a.size) print(len([a.Table[i] for i in range(a.size)])) # 最后记得释放内存 ct.free(a.Table)
注意事项
- 方案2需要手动管理内存,
ct.malloc分配的内存必须用ct.free释放,避免内存泄漏。 - 动态重建类的方式更贴近原有代码逻辑,但每次修改尺寸都需重新创建实例;指针方式灵活性更高,但需要额外处理内存操作。
内容的提问来源于stack exchange,提问作者Isaac Zaiek
相关产品推荐
相关产品推荐

