2

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.

1 Answer 1

2

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)]
Sign up to request clarification or add additional context in comments.

3 Comments

Oh my. Thank you very much. Do you have any explanation for previous behaviour of constructor when bit fields are used vs are not? Can it be that init method is passed already constructed ctypes.c_short object that cannot be resized or something alike?
@JakubŁanecki You're previous constructor was calling super() on the base class which doesn't know about x and y.
I don't think that's correct. When I remove bit field specifications from fields both initialization of single object as well as an array work perfectly, issue arises with bit fields only. Also, I believe it suffices to give super() the name of child class (that's what we do) for it to know about x and y.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.