pythonctypes

Cannot create pointer type for a Structure subclass within __init_subclass__ using ctypes.POINTER


import ctypes
from ctypes.wintypes import *

class xStructure(ctypes.Structure):
    def __init_subclass__(cls):
        p = ctypes.POINTER(cls)

class OFFSET_VAL_II_C4(xStructure):
    _fields_ = [('ValOrig', DOUBLE),
                ('MaxChangeVal', DOUBLE),
                ('ActChangeVal', DOUBLE)]

The code above results in TypeError: _type_ must have storage info triggered by line 6 (p = ctypes.POINTER(cls)).

However, the code below works just fine.

import ctypes
from ctypes.wintypes import *

class xStructure(ctypes.Structure):
    pass

class OFFSET_VAL_II_C4(xStructure):
    _fields_ = [('ValOrig', DOUBLE),
                ('MaxChangeVal', DOUBLE),
                ('ActChangeVal', DOUBLE)]

p = ctypes.POINTER(OFFSET_VAL_II_C4)

My researches into the specific TypeError suggested it is caused by an attempt to create a pointer type for something that is not a ctype. However, in this case, OFFSET_VAL_II_C4 is a subclass of ctypes.Structure (via xStructure), so should work - and does, as evidenced by the second example.

What am I missing?

(For those wondering why I want to do this, there is a reason that I am happy to elaborate, but I am well aware that the minimal code here is utterly pointless!)


Solution

  • In your first example, when __init_subclass__ runs for OFFSET_VAL_II_C4, the class object (cls) exists but _fields_ has not yet been set - because __init_subclass__ runs before the class body is fully executed. So from ctypes's point of view, cls is not yet a complete structure - it has no storage info thus you get this error.

    Whereas in second example you're calling ctypes.POINTER() after the structure is fully defined and _fields_ has been set - so it works fine.

    If your goal is to automatically define a pointer type for each subclass, you can delay the POINTER(cls) call until after the class is completely built. This can be done using a metaclass - a more advanced way like this -

    import ctypes
    from ctypes.wintypes import *
    
    class AutoPointerMeta(type(ctypes.Structure)):
        def __init__(cls, name, bases, clsdict):
            super().__init__(name, bases, clsdict)
            if hasattr(cls, "_fields_"):
                cls.PTR = ctypes.POINTER(cls)
    
    class xStructure(ctypes.Structure, metaclass=AutoPointerMeta):
        pass
    
    class OFFSET_VAL_II_C4(xStructure):
        _fields_ = [('ValOrig', DOUBLE),
                    ('MaxChangeVal', DOUBLE),
                    ('ActChangeVal', DOUBLE)]
    
    # Usage:
    p = OFFSET_VAL_II_C4.PTR