Introspecting nested ctypes structures

1.4k views Asked by At

Similarly to this question, I would like to print the members of a C structure from Python.

I implemented the following function:

def print_ctypes_obj(obj, indent=0):
    for fname, fvalue in obj._fields_:
        if hasattr(fvalue, '_fields_'):
            print_ctypes_obj(fvalue, indent+4)
        else:
            print '{}{} = {}'.format(' '*indent, fname, getattr(obj, fname))

The idea is that if the field itself has a _fields_ attribute, then it is a structure, otherwise a normal field so print it. The recursion works fine, but after the first level I'm getting repr strings printed instead of values. For example:

foo = 1
bar = 2
baz = 3
    innerFoo = <Field type=c_long, ofs=0, size=4>
    innerBar = <Field type=c_long, ofs=4, size=4>
    innerBaz = <Field type=c_long, ofs=8, size=4>
quz = 4

The output I'm expecting would be something like:

foo = 1
bar = 2
baz = 3
    innerFoo = 5
    innerBar = 23
    innerBaz = 56
quz = 4

What is my mistake here?

3

There are 3 answers

0
Tamás Szelei On BEST ANSWER

The solution is pretty simple.

When printing the nested structures, I still need to get the structure as an attribute so ctypes can perform its magic:

print_ctypes_obj(getattr(obj, fname), indent+4)

(another issue with the code is the naming of the iterated pairs; they should be fname, ftype instead of fname, fvalue which is incorrect and misleading)

0
a_bet On

I also found this thread useful. Wrote a slight variation where a string is returned, so that its print can be handled separately.

def ctypes_obj_to_str(obj, level=0):

    delta_indent="  "
    indent=delta_indent*level

    ret_str = ''

    # Assess wether the object is an array, a structure or an elementary type
    if issubclass(type(obj), ctypes.Array) :
        ret_str += f'{indent}ARRAY {obj}\n'
        for obj2 in obj:
            ret_str += ctypes_obj_to_str(obj2, level+1)
        return ret_str

    elif hasattr(obj, '_fields_'):
        ret_str += f'{indent}STRUCTURE {obj}\n'
        for fdesc in obj._fields_:
            # Get the next field descriptor
            fname = fdesc[0]
            ftype = fdesc[1]
            if len(fdesc)==3:
                fbitlen = fdesc[2]
            else:
                fbitlen = 8*ctypes.sizeof(ftype)
            obj2 = getattr(obj, fname)
            ret_str += f'\n{indent+delta_indent}FIELD {fname} {get_c_type_str(ftype)[0]} {fbitlen} bits\n'
            ret_str += ctypes_obj_to_str(obj2, level+2)
        return ret_str

    else:
        return f'{indent}VALUE = {obj} (type={type(obj)})\n'
0
Marc On

I found this thread very useful, so I add this improved code to support the arrays :

#
def print_ctypes_obj(obj, level=0):

    delta_indent="    "
    indent=delta_indent*level

    # Assess wether the object is an array, a structure or an elementary type
    if issubclass(type(obj), ctypes.Array) :
        print('{}ARRAY {}'.format(indent, obj))
        for obj2 in obj:
            print_ctypes_obj(obj2, level+1)

    elif hasattr(obj, '_fields_'):
        print('{}STRUCTURE {}'.format(indent, obj))
        for fdesc in obj._fields_:
            # Get the next field descriptor
            fname = fdesc[0]
            ftype = fdesc[1]
            if len(fdesc)==3:
                fbitlen = fdesc[2]
            else:
                fbitlen = 8*ctypes.sizeof(ftype)
            obj2 = getattr(obj, fname)
            print('{}FIELD {} (type={}, bitlen={})'.format(indent+delta_indent, fname, ftype, fbitlen))
            print_ctypes_obj(obj2, level+2)

    else:
        print('{}VALUE = {} (type={})'.format(indent, obj, type(obj)))