'How to assign value to zero-length array defined by ctypes in Python3

Here I have a class defined like below, whose attribute data is a zero-length array. How to instantiate this class?

class Frame(ctypes.Structure):
    _fields_ = [
        ("id", ctypes.c_uint64),
        ("size", ctypes.c_uint32),
        ("data", ctypes.c_byte*0)       # zero-length array
    ]

I tried this

frame = Frame()
frame.id = 0
frame.size = 2
frame.data = ctypes.cast(b'12', ctypes.POINTER(ctypes.c_type * 0))

but an exception was raised at line 4

TypeError: incompatible types, LP_c_byte_Array_0 instance instead of c_byte_Array_0 instance

so, how should I do to instantiate this class correctly?



Solution 1:[1]

Listing [Python.Docs]: ctypes - A foreign function library for Python.

This technique (having a struct with a 0 lengthed array as last member) is commonly used in C in order to have variable data right after the struct.
Unfortunately, the Python layer doesn't allow that (assigning to an array a value larger than its length). The closest thing to your goal would be to declare the data member as a pointer. Going further, create a setter method that does all conversions:

code00.py:

#!/usr/bin/env python3

import ctypes as ct
import sys


class Frame(ct.Structure):
    _fields_ = [
        ("id", ct.c_uint64),
        ("size", ct.c_uint32),
        ("data", ct.POINTER(ct.c_ubyte)),  # Make it a pointer
    ]

    def set_data(self, value):
        if not isinstance(value, (bytes,)):
            raise ValueError("Bytes expected.")
        self.data = ct.cast(value, ct.POINTER(ct.c_ubyte))
        self.size = len(value)


def main(*argv):
    frame = Frame()
    frame.set_data(b"123abCD")
    for i in range(frame.size):
        print("{0:d} - {1:d}".format(i, frame.data[i]))


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

Output:

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q059172596]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code00.py
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 064bit on win32

0 - 49
1 - 50
2 - 51
3 - 97
4 - 98
5 - 67
6 - 68

Done.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1