Search code examples
pythonstructurectypesbit-fields

Python ctypes: structure with bit fields initialization


I noticed that I cannot default initialize an object of ctypes.Structure derived class when it has bit fields in it, but I can default initialize an array of such objects.

Let's say we define such a class:

class What(ctypes.Structure):
    _fields_ = [('x', ctypes.c_float), ('y', ctypes.c_short, 2)]

    def __init__(self, x=None, y=None):
        if not x:
            x = ctypes.c_float()
        if not y:
            y = ctypes.c_short()
        super(What, self).__init__(x, y)

Now this code executes smoothly and to my understanding, it makes use of the default constructor defined above.

what_arr = What * 4
w_arr = what_arr()

An array of zero-filled structs is returned. However, when I try to initialize just one object, I get an 'access violation reading location' error and the program crashes.

w = What()

It would be great if someone explained what is going under the hood and what is the reason for this behavior.

A couple more details:

I need to have

x = ctypes.c_float()
y = ctypes.c_short()

instead of directly passing 0s to initialization, because normally fields of the structure contain other structures and I want to use their default constructors here too (so that everything is recursively initialized with 0s).

I believe that this may be useful for people who want to test some wrapped package with dummy values first.


Solution

  • The base class doesn't know what x and y are. If I understand the OP correctly the default behavior of ctypes.Structure is all that is needed. I added a __repr__ function to more easily see what is happening:

    class What(ctypes.Structure):
        _fields_ = [('x', ctypes.c_float), ('y', ctypes.c_short, 2)]
    
        def __repr__(self):
            return f'What(x={self.x},y={self.y})'
    

    Testing...

    >>> w = What()
    >>> w
    What(x=0.0,y=0)
    >>> w = What(1.5)
    >>> w
    What(x=1.5,y=0)
    >>> w = What(1.5,2)
    >>> w
    What(x=1.5,y=-2)
    >>> wa = (What*4)()
    >>> list(wa)
    [What(x=0.0,y=0), What(x=0.0,y=0), What(x=0.0,y=0), What(x=0.0,y=0)]
    

    Note also that ctypes structures are zero-initialized by default, so you don't need any magic here either even for nested structures:

    import ctypes
    
    class Inner(ctypes.Structure):
        _fields_ = [('a',ctypes.c_int),('b',ctypes.c_int)]
    
        def __repr__(self):
            return f'Inner(a={self.a},b={self.b})'
    
    class What(ctypes.Structure):
        _fields_ = [('x', Inner), ('y', ctypes.c_short, 2)]
    
        def __repr__(self):
            return f'What(x={self.x},y={self.y})'
    

    Testing...

    >>> w = What()
    >>> w
    What(x=Inner(a=0,b=0),y=0)
    >>> wa = (What*4)()
    >>> list(wa)
    [What(x=Inner(a=0,b=0),y=0), What(x=Inner(a=0,b=0),y=0), What(x=Inner(a=0,b=0),y=0), What(x=Inner(a=0,b=0),y=0)]