FAQ: how to vary the byte offset of a field of a ctypes.Structure

p.lavarre at ieee.org p.lavarre at ieee.org
Thu May 31 17:36:19 EDT 2007


""" Thomas,

Ouch ouch I must have misunderstood what you meant by "use the dynamic
nature of Python, and (re-)define the data type after the required
size is already known, on a case by case basis".

Do you have an example of what you meant? I searched but did not find.
Are those your words?

Yes, to declare strings of strings in Python would be to express a
familiar idea that I don't know how to say well in C.

These are standard structs that I exchange with third parties, e.g.,
me editing Class files read later by a Java interpreter, so I can't
avoid having to deal with this complexity designed into them. For
discussion I've simplified the problem: back in real life I have a few
dozen variations of structs like this to deal with.

The following Python mostly works, but only at the cost of rewriting
the small part of CTypes that I need for this app.
"""

import binascii
import struct

class Struct:

        def __init__(self, declarations = []):

	        """Add initial values to self and list the names declared."""

                names = []
                for (initial, name) in declarations:
                        names += [name]
                        python = ' '.join(['self.' + name, '=',
'initial'])
                        exec python
                self._names_ = names

        def _fields_(self):

	        """List the fields."""

                fields = []
                for name in self._names_:
                        python = 'self.' + name
                        fields += [eval(python)]
                return fields

        def _pack_(self):

	        """Pack a copy of the fields."""

                packs = ''
                for field in self._fields_():
                        packs += field._pack_()
                return packs

        def _size_(self, bytes = None):

	        """Count the bytes of the fields."""

                return len(self._pack_())

        def _unpack_(self, bytes):

	        """Count the bytes of a copy of the fields."""

                offset = 0
                for field in self._fields_():
                        size = field._size_(bytes[offset:])
                        field._unpack_(bytes[offset:][:size])
                        offset += size
                if offset != len(bytes):
                        why = ('_unpack_ requires a string argument'
                                'of length %d' % offset)
                        raise struct.error(why)

class Field(Struct):

        """Contain one value."""

        def __init__(self, value = 0):
                self._value_ = value

        def _pack_(self):
                raise AttributeError('abstract')

        def _unpack_(self, bytes):
                raise AttributeError('abstract')

class Byte(Field):

        """Contain one byte."""

        def _pack_(self):
                return struct.pack('B', self._value_)

        def _unpack_(self, bytes):
                self._value_ = struct.unpack('B', bytes)[0]

class ByteArray(Field):

        """Contain the same nonnegative number of bytes always."""

        def _pack_(self):
                return self._value_

        def _unpack_(self, bytes):
                if len(bytes) == len(self._value_):
                        self._value_ = bytes
                else:

                        why = ('_unpack_ requires a string argument'
                                'of length %d' % len(self._value_))
                        raise struct.error(why)

class Symbol(Struct):

        """Contain a count of bytes."""

        def __init__(self, value = ''):
                Struct.__init__(self, [
                        (Byte(), 'length'),
                        (ByteArray(value), 'bytes')])
                self._pack_()


        def _size_(self, bytes = None):
                return ord(bytes[0])

        def _pack_(self):
                self.length = Byte(self.length._size_() +
self.bytes._size_())
                return Struct._pack_(self)

class TwoSymbols(Struct):

        """Contain two Symbols."""

        def __init__(self, values = ['', '']):
                Struct.__init__(self, [
                        (Symbol(values[0]), 'first'),
                        (Symbol(values[1]), 'second')])

zz = Symbol()
print binascii.hexlify(zz._pack_()).upper()
# 01

zz = Symbol()
zz.bytes = ByteArray('left')
zz._unpack_(zz._pack_()) ; print repr(zz._pack_())
# '\x05left'

zz = Symbol()
zz.bytes = ByteArray('right')
zz._unpack_(zz._pack_()) ; print repr(zz._pack_())
# '\x06right'

zz = TwoSymbols()
zz.first.bytes = ByteArray('hi')
zz.second.bytes = ByteArray('bye')
zz._unpack_(zz._pack_()) ; print repr(zz._pack_())
# '\x03hi\x04bye'

print zz._size_()
# 7

yy = '''
def yyFunc(self):
	return [self.length, self.bytes]
'''
exec yy
Symbol._fields_ = yyFunc
zz = TwoSymbols(['alef', 'bet'])
zz._unpack_(zz._pack_()) ; print repr(zz._pack_())




More information about the Python-list mailing list