Python 带有结构数组的 ctype 结构

Python ctype struct with array of structs

我有一些结构,具有动态条目数。我从 UDP 接收 bytearray 并按如下方式解析此消息:

class MsgStruct(Structure):
    _pack_ = 1

    def __init__(self, data=None):
        if data:
            self.unpack(data)

    def unpack(self, raw):
        fit = sizeof(self)
        memmove(addressof(self), raw[:fit], fit)

    def pack(self):
        return bytearray(self)[:]

    def size(self):
        return sizeof(self)


class MessageEntry(MsgStruct):

    _fields_ = [
        ('type', c_byte),
        ('flag', c_byte),
        ('count', c_int)]


class Message(MsgStruct):

    _fields_ = [
        ('id', c_int),
        ('entry_count', c_int)]

    entries = []

    def __init__(self, data=None):
        MsgStruct.__init__(self, data=data)
        if data:
            self.parseEntries(data[self.entry_count:])

    def parseEntries(self, data):
        offset = 0
        size = sizeof(MessageEntry())
        for count in range(self.entry_count):
            entry = MessageEntry(data[offset:offset+size])
            self.entries.append(entry)
            offset += size

但我认为有更好的方法可以使用 ctypes.Array 或 POINTER 解析消息并尝试这样做:

class Message(MsgStruct):

    _fields_ = [
        ('id', c_int),
        ('entry_count', c_int),
        ('entries', POINTER(MessageEntry))]

    def __init__(self, data=None):
        MsgStruct.__init__(self, data=data)
        if data:
            self.parseEntries(data[self.entry_count:])

    def parseEntries(self, data):
        offset = 0
        size = sizeof(MessageEntry())
        elems = (MessageEntry * self.entry_count)()
        self.entries = cast(elems, POINTER(MessageEntry))
        for count in range(self.entry_count):
            self.entries[count] = MessageEntry(data[offset:offset+size])
            offset += size

但是当我尝试打印条目时,我陷入了无限循环

msg = Message(x)
for i in msg.entries:
    print(i)

我做错了什么? 是否有另一种方法来解析带有动态条目的消息?

我想首先说明一下,我没有看到 entry_count 属性在哪里初始化。

将指针当作 大小的 数组进行迭代在概念上是错误的(正如 [Python 3]: ctypes - A foreign function library for Python 中所指出的)。在 C 中,可以超出数组边界,但 ctypes 禁止这样做。
这里有一个更简单的例子,它使用 ctypes.c_char 作为基本类型(MessageEntry 对应)。

code.py:

#!/usr/bin/env python3

import sys
import ctypes


def main():
    CharArr5 = ctypes.c_char * 5
    b5 = b"12345"
    ca5 = CharArr5(*b5)
    print("Print array ...")
    for c in ca5:
        print(c)

    cp = ctypes.cast(ca5, ctypes.POINTER(ctypes.c_char))
    max_values = 10
    print("\nPrint pointer (max {:d} values) ...".format(max_values))
    for idx, c in enumerate(cp):
        print(c)
        if idx >= max_values:
            print("Max value number reached.")
            break


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

输出:

(py_064_03.06.08_test0) e:\Work\Dev\Whosebug\q054178876>"e:\Work\Dev\VEnvs\py_064_03.06.08_test0\Scripts\python.exe" code.py
Python 3.6.8 (tags/v3.6.8:3c6b436a57, Dec 24 2018, 00:16:47) [MSC v.1916 64 bit (AMD64)] on win32

Print array ...
b'1'
b'2'
b'3'
b'4'
b'5'

Print pointer (max 10 values) ...
b'1'
b'2'
b'3'
b'4'
b'5'
b'\x00'
b'\x00'
b'\x00'
b'\x00'
b'\x00'
b'\x00'
Max value number reached.

如上所示, 可以遍历指针,但循环永远不会结束(当到达不可用/无效的内存地址时它将结束,并且程序将 segfault访问冲突))。

假设entry_count正确初始化(如果没有,请确保初始化它),用它来保持边界内的循环(如下所示):

for idx in range(msg.entry_count):
    msg.entries[idx]  # Do smth with it
    # ...


for idx, entry in enumerate(msg.entries):
    if idx >= msg.entry_count:
        break
    entry  # Do smth with it
    # ...

或者您可以使用上述方法之一为 Message.

实现 Iterator Protocol