ctypes:结构,size_t 字段

ctypes: structure, size_t field

我正在尝试通过 ctypeslibnfc 实现 python-to-c 绑定。 我有一个结构,这是一个错误的变体:

class nfc_iso14443a_info(Structure):
    _fields_ = [
        ('abtAtqa',  c_uint8 * 2),
        ('btSak',    c_uint8),
        ('szUidLen', c_uint8 * 8),
        ('abtUid',   c_uint8 * 10),
        ...

在调试会话期间的某个时刻,它看起来像这样:

这里的问题是我希望 szUidLen 是等于 7 的 64 位无符号整数。更准确地说,它必须匹配 nfc-types.h 中的 size_t szUidLen;。所以我尝试了一个明显的变体并将 c_uint8 * 8 更改为 c_size_t 但它不起作用:

class nfc_iso14443a_info(Structure):
    _fields_ = [
        ('abtAtqa',  c_uint8 * 2),
        ('btSak',    c_uint8),
        ('szUidLen', c_size_t),
        ('abtUid',   c_uint8 * 10),
        ...

我在这里错过了什么?

这里的问题是您尝试映射的 C 结构已打包,如文档的 Structure/union alignment and byte order 部分中(简洁地)解释的那样:

By default, Structure and Union fields are aligned in the same way the C compiler does it. It is possible to override this behavior be specifying a _pack_ class attribute in the subclass definition. This must be set to a positive integer and specifies the maximum alignment for the fields. This is what #pragma pack(n) also does in MSVC.

只有您已经了解 C 语言中的打包和对齐才有意义,但它并没有那么复杂。

默认情况下,C 结构元素对齐以从漂亮的边界开始。例如,8 位 int 之后的 32 位 int 不是 运行 来自字节 1-4,而是 运行 来自字节 4-7(字节 1-3 是未使用的填充) .因此,ctypes 遵循相同的规则。

这意味着,虽然 szUidLen 运行 来自字节 3-10,当它被定义为 8 位整数数组时,它会与字节 8-15(或 4- 11,取决于您的编译器)当它被定义为 64 位 int 时。您可以通过打印出 nfc_iso14443a_info.szUidLen.offset.

来看到这一点

因此,第一个获取字节 7, 0, 0, 0, 0, 0, 0, 0,即 little-endian int64 for 7,而第二个获取字节 0, 0, 0, a, b, c, d, e,其中 abcde 是下一个字段的前 5 个字节,对于某个巨大的数字,它是 little-endian int64(除非下一个字段恰好为 0)。

当然你不想只是猜测这就是问题所在。如果你的 Structure 基于来自 C 头文件的 struct,那么只有当头文件或编译标志指定一些 non-default 包装时,这才会是真的,比如使用的 #pragma pack(1)通过 MSVC。如果你的 Structure 是基于类似 RFC 数据包描述的东西,对齐甚至不符合 C 规则,而是在你正在阅读的文档中的某处定义(尽管协议 RFC 几乎总是使用 1 字节对齐) .

无论如何,文档并没有很好地解释问题,但他们解释了解决方案:

class nfc_iso14443a_info(Structure):
    _pack_ = 1
    _fields_ = [
        ('abtAtqa',  c_uint8 * 2),
        ('btSak',    c_uint8),
        ('szUidLen', c_size_t),
        ('abtUid',   c_uint8 * 10),
        ...

现在 szUidLen 运行 来自字节 3-10,但它被解释为 64 位整数而不是 8 位整数数组。

from ctypes import *
c_size_t = c_unit64

继续 您 可能 也需要指定 ._pack_=1(如果您的编译器以这种方式生成代码) 定义 _fields_.

更新: 有现成的 c_size_t(和 c_ssize_t)输入 ctypes.

注:(c_char * 8)等于c_int64c_long因为可能对齐问题(c_char 字段未对齐)。 ctypes.alignment(c_type) 可能会提示您如何对齐 c_type

In [7]: c.alignment(c.c_char * 8), c.alignment(c.c_size_t)
Out[7]: (1, 8)