【发布时间】:2018-07-03 13:43:25
【问题描述】:
我必须读取从 dll 返回的 C 结构数组并将其转换为 Numpy 数组。代码使用 Python 的 cffi 模块。
到目前为止,代码可以工作,但我不知道如何处理 np.frombuffer 抱怨的结构中的成员填充:
ValueError: 缓冲区大小必须是元素大小的倍数
这是我的代码:
from cffi import FFI
import numpy as np
s = '''
typedef struct
{
int a;
int b;
float c;
double d;
} mystruct;
'''
ffi = FFI()
ffi.cdef(s)
res = []
#create array and fill with dummy data
for k in range(2):
m = ffi.new("mystruct *")
m.a = k
m.b = k + 1
m.c = k + 2.0
m.d = k + 3.0
res.append(m[0])
m_arr = ffi.new("mystruct[]", res)
print(m_arr)
# dtype for structured array in Numpy
dt = [('a', 'i4'),
('b', 'i4'),
('c', 'f4'),
('d', 'f8')]
# member size, 20 bytes
print('size, manually', 4 + 4 + 4 + 8)
# total size of struct, 24 bytes
print('sizeof', ffi.sizeof(m_arr[0]))
#reason is member padding in structs
buf = ffi.buffer(m_arr)
print(buf)
x = np.frombuffer(buf, dtype=dt)
print(x)
任何想法如何以干净的方式处理这个问题?
编辑:
如果我在应该发生填充的 dtype 中添加一个额外的数字,它似乎可以工作:
dt = [('a', 'i4'),
('b', 'i4'),
('c', 'f4'),
('pad', 'f4'),
('d', 'f8')]
为什么填充发生在那里? (Win7,64 位,Python 3.4 64 位)。
但这不是最好的方法。真正的代码要复杂得多,动态得多,所以应该可以以某种方式处理它,对吧?
【问题讨论】:
-
我发现“一般来说,结构实例将具有其最宽标量成员的对齐方式。” (catb.org/esr/structure-packing/#_structure_reordering) 但后来他们提到可以重新排序结构。但是,如果每个成员都占用相同的空间,为什么会这样呢?
-
最有可能的是,正如您自己提到的,C 结构被填充。对于这实际上意味着什么,请查看this post。因此,当您定义编辑中提到的 dt 时,您可以通过自己插入“间隙”来对齐填充结构。 DLL 的程序员需要打包结构,以便您使用原始 dt 定义。
-
您的成员
d似乎是一个 64 位浮点数(可能是 Cdouble)。您在编辑中呈现的布局可确保当整个结构同样对齐时,该成员在 8 字节边界上对齐,并且还确保该类型的相邻数组元素都可以是 8 字节对齐。 -
但是,如果像通常建议的那样,将最大的成员放在第一位,那么仍然会有一个尾随填充。如果我明白了这个概念,那么如果排序顺序是
double, int, int, float,那么仍然有四个字节可以将间隙填充到 double 的倍数。对吗? -
是的,乔,这是正确的。重新排序元素可能避免它们之间的填充,但如果编译器正在布局结构以确保
double可以在 8 字节边界上对齐,那么它必须要求整个结构具有8 字节(至少)对齐要求。并且类型表示的大小必须始终是其对齐要求的倍数,否则您无法形成该类型的数组。
标签: python c numpy struct python-cffi