2

I'm trying to create a ctypes generic structure which has the behavior of a dynamic array. I'm using the following SO answer as a base: https://stackoverflow.com/a/42843610/1935787

It works when I instantiate the class independently, but when I use this class as field of another class, the from_buffer_copy does not call the __new__ class method, so my class is not creating the array.

Code:

import ctypes

class SizedStructure(ctypes.LittleEndianStructure):
   _sized_vars_ = []  # (var_name, var_type, size_var_id, (size_var_path,), size_var_granularity)

   @classmethod
   def from_buffer_copy(cls, buff):
      obj = type(cls).from_buffer_copy(cls, buff)
      values = cls.get_object_sized_vars_values(obj)
      instance = cls.create_inner_struct(values).from_buffer_copy(buff)
      return instance

   @classmethod
   def create_inner_struct(cls, values):
      fields = cls._fields_[:]
      class_name = cls.__name__
      for var_name, var_type, size_var_id, _, size_var_granularity in cls._sized_vars_:
         if size_var_id not in values:
            raise Exception("'{}' not passed".format(size_var_id))
         fields.append((var_name, var_type * (values[size_var_id] * size_var_granularity)))
         class_name = class_name + "_{}_{}".format(size_var_id, values[size_var_id])

      class Temp(ctypes.LittleEndianStructure):
         _pack_ = 1
         _fields_ = fields

         def __init__(self, *args, **kwargs):
            super(Temp, self).__init__(*args, **kwargs)
            for key, value in kwargs.items():
               setattr(self, key, value)

      return type(class_name, (Temp,), {})

   @classmethod
   def get_object_sized_vars_values(cls, obj):
      values = {}
      for _, _, size_var_id, size_var_path, _ in cls._sized_vars_:
         curr = obj
         for part in size_var_path:
            curr = getattr(curr, part)
         values[size_var_id] = curr
      return values

class DynamicLengthArray(SizedStructure):
   _pack_ = 1
   _fields_ = [
         ("size",   ctypes.c_uint32),
      ]
   _sized_vars_ = [("data", ctypes.c_ubyte, "size", ("size",), ctypes.sizeof(ctypes.c_uint8))]

class DynamicLengthArrayParent(ctypes.LittleEndianStructure):
   _pack_ = 1
   _fields_ = [
         ("id",  ctypes.c_uint32),
         ("arr", DynamicLengthArray),
      ]

a = DynamicLengthArray.from_buffer_copy(b"\x02\x00\x00\x00\x33\x44")
assert(a.size == 2)
assert(a.data[0] == 0x33)
assert(a.data[1] == 0x44)
print(a.data)

b = DynamicLengthArrayParent.from_buffer_copy(b"\x78\x56\x34\x12\x02\x00\x00\x00\x33\x44")
assert(b.id == 0x12345678)
assert(b.arr.size == 2)
assert(b.arr.data[0] == 0x33)
assert(b.arr.data[1] == 0x44)
print(b.arr.data) 

Result:

<__main__.c_ubyte_Array_2 object at 0x00000000029EB6C8>
Traceback (most recent call last):
  File "c:/Temp/so.py", line 67, in <module>
    assert(b.arr.data[0] == 0x33)
AttributeError: 'DynamicLengthArray' object has no attribute 'data'

How can I trigger the __new__ method by calling from_buffer_copy ?

Aviv Cohen
  • 108
  • 6

0 Answers0