Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python ctypes bitfields

Tags:

python

ctypes

I need a bitfield definition that's compatible with ctypes Structures, so that I can use it as a pointer to a memory-mapped set of hardware registers, i.e.

class RegFile(Structure):
    _fields_ = [
        ('ticks', c_uint32),
        ('id', id_bitfield),
    ]

Ideally they'd be very Pythonic things, and behave mostly like dicts. I've managed to work most of the gory details out to where I now have a factory function that makes bitfield classes similarly to making namedtuples. Here's an example of it ripping apart the fields of a standard float.

def make_register(name, fields, basetype=c_uint32):
    # Define the underlying bitfield type
    bitfield = type(name + '_bitfield', (LittleEndianStructure, ), {})
    bitfield._pack_ = 1
    bitfield._fields_ = fields

    # Set up the union
    d = {
        '_fields_' : [('base', basetype), ('_b', bitfield)],
        '_anonymous_' : ('_b',),
        '__iter__' : _register_iter,
        'keys' : _register_keys,
        'items' : _register_items,
        'update' : _register_update
    }
    return type(name, (Union, ), d)

ieee754_fields = [
    ('mantissa', c_uint, 23),
    ('exponent', c_uint, 8),
    ('sign', c_uint, 1)
]
IEEE754 = make_register('IEEE754', ieee754_fields, c_float)
x = IEEE754()

This works decently, but isn't a particularly Pythonic feeling syntax. Ideally I'd have some answer that allows me to define the bitfield class as:

class IEEE754(Register):
    """Individual bitfields of a standard IEEE-754 floating point number."""
    _fields_ = ieee754_fields
    _basetype_ = c_float

But I haven't been able to get that Register class to be a thing. It seemed like Register should inherit from Union and have some metaclass magic applied, but that way metaclass conflicts against Union lie. Any thoughts? Or should I just stick with what I've got?

like image 929
Rgaddi Avatar asked Dec 04 '13 16:12

Rgaddi


1 Answers

You can achieve what you describe by creating a Register metaclass that extends type(ctypes.Union) as below:

class Register(type(ctypes.Union)):
    def __new__(mcs, name, bases, dict):
        #make the bitfield class
        class regBitfield(ctypes.LittleEndianStructure):
            _fields_ = dict['_fields_']
        #set up the Union
        class regUnion(ctypes.Union):
            _fields_ = [('base', dict['_basetype_']), ('_b', regBitfield)]
            _anonymous_ = ('_b', )
            __iter__ = _register_iter
            keys = _register_keys
            items = _register_items
            update = _register_update
        #return a subclass of the temporary regUnion class
        return type(ctypes.Union).__new__(mcs, name, (regUnion,), dict)

class IEEE754: #for Python3 use class IEEE754(metaclass=Register):
    __metaclass__ = Register #omit this line Python3
    _fields_ = [('mantissa', ctypes.c_uint, 23),
                ('exponent', ctypes.c_uint, 8),
                ('sign', ctypes.c_uint, 1)
               ]
    _basetype_ = ctypes.c_float

This works in almost the same way as your make_register function, but utilizes the existing machinery of the class system and the special rules for extending ctypes.Union and ctypes.Structure. Specifically, any subclass of a subclass of ctypes.Union or ctypes.Structure will extend the _fields_ list, rather than replace it. Knowing this, we can create temporary classes within the __new__ of our Register metaclass that the ultimate class extends. With the above:

>>> myFloat = IEEE754(42.0)
>>> print(myFloat.base)
42.0
>>> print(myFloat.mantissa)
2621440L
>>> fltReg._fields_
[('mantissa', <class 'ctypes.c_ulong'>, 23), ('exponent', <class 'ctypes.c_ulong'>, 8), ('sign', <class 'ctypes.c_ulong'>, 1)]

The Register.__new__ method might break if the type being created has multiple bases.

like image 103
multipleinterfaces Avatar answered Nov 03 '22 06:11

multipleinterfaces