【问题标题】:Python ctypes struct with flexible array member to bytearrayPython ctypes struct 具有灵活的数组成员到 bytearray
【发布时间】:2020-12-30 08:44:40
【问题描述】:

我正在编写一个 Python 应用程序,它将编写一个二进制文件。该文件将由在嵌入式目标上运行的一些 C 代码解析。

我相信我可以通过从 Struct class 派生来做到这一点,但是打包格式很糟糕,而且我的所有结构都是 little-endian,所以我想到了使用 ctypes 包。

假设我有以下 C 结构:

struct my_c_struct
{
    uint32_t    a;
    uint16_t    b;
    uint16_t    table[];
};

在 C 端,我使用转换为内存缓冲区的指针对该结构进行操作,所以我可以这样做:

uint8_t buf[128];
struct my_c_struct *p = (struct my_c_struct*) buf;
p->table[0] = 0xBEEF;

如何在 Python 中最好地表示这一点?我的第一个尝试是:

class MyCStruct(ctypes.LittleEndianStructure):

    c_uint32 = ctypes.c_uint32
    c_uint16 = ctypes.c_uint16
    
    _pack_ = 1

    _fields_ = [
        ("a", c_uint32),
        ("b", c_uint16),
    ]

    def __init__(self, a, b):
        """
        Constructor
        """
        super(ctypes.LittleEndianStructure, self).__init__(a, b)
        self.table = []

    def pack(self):
        data = bytearray(self.table)
        return bytearray(self)+data

pack() 方法背后的想法是,它将在结构的末尾组装可变长度表。请注意,我不知道 table 在创建对象时有多少条目。

我实现它的方式显然行不通。所以我正在考虑将 ctypes-devived 类嵌套在纯 Python 类中:

class MyCStruct:

    class my_c_struct(ctypes.LittleEndianStructure):
        _pack_ = 1
        _fields_ = [ ("a", ctypes.c_uint32),
                     ("b", ctypes.c_uint16) ]


    def __init__(self, a, b):
        """
        Constructor
        """
        self.c_struct = self.my_c_struct(a,b)
        self.table = []
    
    def pack(self):
        self.c_struct.b = len(self.table)
        x = bytearray(self.c_struct)
        y = bytearray()
        for v in self._crc_table:
            y += struct.pack("<H", v)
        return x + y

这是一个好方法吗?我不想深入兔子洞,只是为了发现有更好的方法。

警告:我正在使用 Python 2(请不要问...),因此仅 Python 3 的解决方案对我没有用,但对宇宙的其他部分有用。

干杯!

【问题讨论】:

  • 不确定这是否有帮助,但您是否查看过 Python 中的 struct 标准库(适用于 2.7)?查看底部的“类”部分。
  • 感谢您的回答!是的,我做到了,但是格式化语法有点糟糕,我试图尽可能地像 Python 一样。
  • 你看过Cap'n Proto吗?它是协议缓冲区的一种变体,旨在减少 CPU 密集度以进行序列化/反序列化。
  • “在 C 端,我使用转换为内存缓冲区的指针对该结构进行操作”——从而获得未定义的行为。你可能会侥幸逃脱,但随着编译器在优化方面变得更聪明、更积极,诸如此类的严格混叠违规似乎越来越频繁地引发不需要的行为。
  • @JohnBollinger pfft,只是 alias gcc="gcc -fno-strict-aliasing" :')

标签: python c ctypes python-2.x


【解决方案1】:

struct 模块真的很容易解决这个问题(Python 2 代码):

>>> import struct
>>> a = 1
>>> b = 2
>>> table = [3,4]
>>> struct.pack('<LH{}H'.format(len(table)),a,b,*table)
'\x01\x00\x00\x00\x02\x00\x03\x00\x04\x00'

使用.format 将16 位值的长度插入table,并使用*table 将table 扩展为正确数量的参数。

使用ctypes 执行此操作更复杂。该函数声明一个具有正确可变数组大小的自定义结构并填充它,然后生成原始数据字节的字节字符串:

#!python2
from ctypes import *

def make_var_struct(a,b,table):
    class Struct(Structure):
        _pack_ = 1
        _fields_ = (('a',c_uint32),
                    ('b',c_uint16),
                    ('table',c_uint16 * len(table)))
    return Struct(a,b,(c_uint16*len(table))(*table))

s = make_var_struct(1,2,[3,4])
print(repr(''.join(buffer(s))))

输出:

'\x01\x00\x00\x00\x02\x00\x03\x00\x04\x00'

【讨论】:

  • 只有在创建对象时知道table的长度时才有效。
  • @Leonardo 在构建这种类型的结构时通常是这种情况。您必须为表分配内存。你的用例是什么?
  • 在实际应用程序中,table 拥有一个用于可变数量数据块的 CRC 表(想想 ELF 文件程序头)。没错,我可以有一个包含 CRC 的单独列表,并且只有在计算完所有这些 CRC 后,才创建打包结构。我想过使用 Python 对象来保存 CRC 表并返回其自身的二进制表示。
猜你喜欢
  • 2020-04-13
  • 1970-01-01
  • 1970-01-01
  • 2020-09-01
  • 2012-11-22
  • 1970-01-01
  • 1970-01-01
  • 2020-07-25
  • 1970-01-01
相关资源
最近更新 更多