Python ctypes可变长度结构
自从我读了Dave Beazley关于二进制I/O处理的文章之后(http://dabeaz.blogspot.com/2009/08/python-binary-io-handling.html)我想为某种有线协议创建一个Python库。然而,我找不到变长结构的最佳解决方案。以下是我想做的:Python ctypes可变长度结构,python,python-3.x,ctypes,Python,Python 3.x,Ctypes,自从我读了Dave Beazley关于二进制I/O处理的文章之后(http://dabeaz.blogspot.com/2009/08/python-binary-io-handling.html)我想为某种有线协议创建一个Python库。然而,我找不到变长结构的最佳解决方案。以下是我想做的: import ctypes as c class Point(c.Structure): _fields_ = [ ('x',c.c_double), ('y',
import ctypes as c
class Point(c.Structure):
_fields_ = [
('x',c.c_double),
('y',c.c_double),
('z',c.c_double)
]
class Points(c.Structure):
_fields_ = [
('num_points', c.c_uint32),
('points', Point*num_points) # num_points not yet defined!
]
由于尚未定义num\u Points
,因此类Points
将不起作用。我可以在知道num\u points
后重新定义\u fields
变量,但由于它是一个类变量,因此会影响所有其他点
实例
这个问题的pythonic解决方案是什么?对于您给出的示例,最简单的方法是在您拥有所需信息时定义结构 一种简单的方法是在您将要使用的位置创建类,而不是在模块根位置创建类-例如,您可以将
类
主体放在函数中,作为工厂-我认为这是最可读的方法
import ctypes as c
class Point(c.Structure):
_fields_ = [
('x',c.c_double),
('y',c.c_double),
('z',c.c_double)
]
def points_factory(num_points):
class Points(c.Structure):
_fields_ = [
('num_points', c.c_uint32),
('points', Point*num_points)
]
return Points
#and when you need it in the code:
Points = points_factory(5)
对不起-
是C代码将为您“填充”这些值-这不是它们的答案。将以另一种方式发布。因此,就像在C中一样,您不能完全按照自己的意愿进行操作。 使用在C中实现所需功能的结构的唯一有用方法是将其作为
结构点{
积分;
点*点;
}
并有实用程序代码来分配你的内存,你可以把你的数据。
除非您有一些安全的maxsize,并且不想为代码的这一部分而烦恼
(内存分配)-然后,代码的网络部分将传输所需的数据
数据来自结构内部,而不是整个结构
要使用结构成员实际包含指向数据所在位置的指针(因此,可能长度可变)的Python ctypes,您还必须手动分配和释放内存(如果您在Python端填充内存),或者只读取数据-创建和销毁数据是在本机代码函数上完成的
因此,创建代码的结构可以是:
import ctypes as c
class Point(c.Structure):
_fields_ = [
('x',c.c_double),
('y',c.c_double),
('z',c.c_double)
]
class Points(c.Structure):
_fields_ = [
('num_points', c.c_uint32),
('points', c.POINTER(Point))
]
管理这些数据结构的创建和删除的代码可以是:
__all_buffers = {}
def make_points(num_points):
data = Points()
data.num_points = num_points
buf = c.create_string_buffer(c.sizeof(Point) * num_points)
__all_buffers[c.addressof(buf)] = buf
p = Point.from_address(c.addressof(buf))
data.points = c.pointer(p)
return data
def del_points(points):
del __all_buffers[c.addressof(m.points[0])
points.num_points = 0
全局变量“\uu all\u buffers”的使用保留了对
python创建了缓冲区对象,以便python不会在
离开make_points结构。另一种方法是获取对
libc(在unix上)或winapi,并自己调用系统的malloc
和free
函数
或者-您可以使用普通的老“struct”Python模块,而不是使用ctypes-
如果您根本没有C代码,并且只使用ctypes作为
“结构”方便。现在,为了完全不同的东西- 如果您只需要处理数据,那么“最具python风格”的方法可能是根本不尝试使用ctypes来处理内存中的原始数据 此方法仅使用struct.pack和.unpack在数据在应用程序上/下移动时序列化/取消序列化数据。“Points”类可以接受原始字节,并从中创建python对象,并可以通过“get_data”方法序列化数据。否则,它只是一个普通的python列表
import struct
class Point(object):
def __init__(self, x=0.0, y=0.0, z= 0.0):
self.x, self.y, self.z = x,y,z
def get_data(self):
return struct.pack("ddd", self.x, self.y, self.z)
class Points(list):
def __init__(self, data=None):
if data is None:
return
pointsize = struct.calcsize("ddd")
for index in xrange(struct.calcsize("i"), len(data) - struct.calcsize("i"), pointsize):
point_data = struct.unpack("ddd", data[index: index + pointsize])
self.append(Point(*point_data))
def get_data(self):
return struct.pack("i", len(self)) + "".join(p.get_data() for p in self)
以下是我到目前为止得出的结论(仍然有点粗糙): 方法
parse
和pack
是泛型的,因此可以将它们移动到元类中。这将使它的使用几乎和我第一次发布的代码片段一样简单
对此解决方案有何评论?仍然在寻找更简单的东西,不确定它是否存在。这个问题真的很老了: 我有一个更简单的答案,这似乎很奇怪,但它避免了元类,解决了ctypes不允许我直接使用与C中相同的定义构建结构的问题 来自内核的示例C结构:
struct some_struct {
__u32 static;
__u64 another_static;
__u32 len;
__u8 data[0];
};
使用ctypes实现:
import ctypes
import copy
class StructureVariableSized(ctypes.Structure):
_variable_sized_ = []
def __new__(self, variable_sized=(), **kwargs):
def name_builder(name, variable_sized):
for variable_sized_field_name, variable_size in variable_sized:
name += variable_sized_field_name.title() + '[{0}]'.format(variable_size)
return name
local_fields = copy.deepcopy(self._fields_)
for variable_sized_field_name, variable_size in variable_sized:
match_type = None
location = None
for matching_field_name, matching_type, matching_location in self._variable_sized_:
if variable_sized_field_name == matching_field_name:
match_type = matching_type
location = matching_location
break
if match_type is None:
raise Exception
local_fields.insert(location, (variable_sized_field_name, match_type*variable_size))
name = name_builder(self.__name__, variable_sized)
class BaseCtypesStruct(ctypes.Structure):
_fields_ = local_fields
_variable_sized_ = self._variable_sized_
classdef = BaseCtypesStruct
classdef.__name__ = name
return BaseCtypesStruct(**kwargs)
class StructwithVariableArrayLength(StructureVariableSized):
_fields_ = [
('static', ctypes.c_uint32),
('another_static', ctypes.c_uint64),
('len', ctypes.c_uint32),
]
_variable_sized_ = [
('data', ctypes.c_uint8)
]
struct_map = {
1: StructwithVariableArrayLength
}
sval32 = struct_map[1](variable_sized=(('data', 32),),)
print sval32
print sval32.data
sval128 = struct_map[1](variable_sized=(('data', 128),),)
print sval128
print sval128.data
对于示例输出:
machine:~ user$ python svs.py
<__main__.StructwithVariableArrayLengthData[32] object at 0x10dae07a0>
<__main__.c_ubyte_Array_32 object at 0x10dae0830>
<__main__.StructwithVariableArrayLengthData[128] object at 0x10dae0830>
<__main__.c_ubyte_Array_128 object at 0x10dae08c0>
machine:~user$python svs.py
这个答案对我有效有两个原因:
我显然更希望头文件带有指针,但这并不总是可能的。这个答案令人沮丧。其他的则非常适合数据结构本身,或者需要修改调用者 您可以使用ctypes指针来执行此操作 C结构
struct some_struct {
uint length;
uchar data[1];
};
Python代码
from ctypes import *
class SomeStruct(Structure):
_fields_ = [('length', c_uint), ('data', c_ubyte)]
#read data into SomeStruct
s = SomeStruct()
ptr_data = pointer(s.data)
for i in range(s.length):
print ptr_data[i]
你为什么需要一个结构?你不能直接发送缓冲区吗?我能问一下C
struct
是什么样子吗?我假设struct{size_t num_points;Point poits[];}
,但我可能错了(特别是如果您的C代码使用C99之前的hacks实现灵活的数组成员行为)。没有单一的C结构等价物。我们需要建立一个缓冲区,然后发送它。@ChrisLutz给出的结构定义实际上是可行的,只要您通过malloc(sizeof(struct whatever)+字节数组使用)创建了指向它的指针。正如您在最后指出的,这不适用于将数据接收到。这让我想到了另一种可能的解决方案,虽然该类返回一个大小合适的内部类。使用Beazley方法的好处是我不必计算大小和打包/解包数据。这是我在C代码中可以做到的。我希望能找到一种方法来利用动态na
from ctypes import *
class SomeStruct(Structure):
_fields_ = [('length', c_uint), ('data', c_ubyte)]
#read data into SomeStruct
s = SomeStruct()
ptr_data = pointer(s.data)
for i in range(s.length):
print ptr_data[i]