具有结构数组的Python ctype结构

时间:2019-01-14 09:39:27

标签: python-3.x ctypes

我有一些结构,带有动态条目数。我从UDP接收字节数组,并按以下方式解析此消息:

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)

我在做什么错? 有没有其他方法可以解析带有动态条目的消息?

1 个答案:

答案 0 :(得分:1)

我想从一个注释开始,我看不到 entry_count 属性在哪里初始化。

在指针上进行迭代,就好像它是一个 size 数组在概念上是错误的(如[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\StackOverflow\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 实现迭代器协议