How do I access values stored in an array inside a C struct returned to Python via ctypes?

Viewed 166

I am trying to learn to use ctypes to populate a linked list of C structs and return the head of the list to Python for post processing. To do that, I have a simple C struct, defined like so:

struct Checkpoint
{
     double *state;
     struct Checkpoint *next;
};
typedef struct Checkpoint checkpoint;

And I have a simple matching Python 3 class defined like so:

class Checkpoint(ct.Structure):
    pass
Checkpoint._fields_ = [('state', ct.POINTER(ct.c_double)),
                       ('next', ct.POINTER(Checkpoint))]

After calling the C code via ctypes to populate a few examples of this structure, I try to print the values in the state array on the python side.

def main():
    # Load the shared library into c types.
    libc = ct.CDLL("./structs.so")
    entry = wrap_function(libc, 'pymain', ct.POINTER(Checkpoint), None)
    get_state = wrap_function(libc, 'get_state', ct.POINTER(ct.c_double), [ct.POINTER(Checkpoint)])
    get_next = wrap_function(libc, 'get_next', ct.POINTER(Checkpoint), [ct.POINTER(Checkpoint)])
    start = entry()
    print(get_state(start).contents);
    start = get_next(start)
    print(get_state(start).contents);

The get_state and get_next functions are as follows:

double *get_state(checkpoint *current)
{
    return current->state;
}

checkpoint *get_next(checkpoint *current)
{
    return current->next;
}

These functions are returning a reference to the correct memory, but I am having trouble accessing anything beyond the first element of the arrays in the structs so defined.

Two questions:

  1. Do I need the get_state and get_next functions, or can I access those parameters directly in Python without doing a call back to C after my structures are defined and populated? If so, how?

  2. My main function in python works to populate the structures as I want them, but only the first value of the state array is printed by the print calls. How to I access the rest of the values in the array?

1 Answers

To answer your first question, I believe the get_state and get_next are not necessary, as you can access the fields of Checkpoint directly

start = entry()
state = start.contents.state
next = start.contents.next

As for the second question, in the case of pointers, .contents will provide the value stored at the pointer's memory address. This means it will only print the first item in the array instead of the entire array. To print the entire array, you must loop through it, printing each element. When only using the pointer, the array's size is unknown, so the first step is to store this count. I think the Checkpoint struct would be a good location.

struct Checkpoint
{
     double *state;
     int state_len;
     struct Checkpoint *next;
};
typedef struct Checkpoint checkpoint;

Then, you'll need to update the python class

class Checkpoint(ct.Structure):
    pass
Checkpoint._fields_ = [('state', ct.POINTER(ct.c_double)),
                       ('state_len', ct.c_int),
                       ('next', ct.POINTER(Checkpoint))]

I'm not sure where your state array is populated, but you'll need to update the state_len wherever you add elements.

Once this is done, you should be able to print the contents using a loop like the following:

start = entry()
for i in range(start.contents.state_len):
    print(start.contents.state[i])

Sources: https://docs.python.org/3/library/ctypes.html#type-conversions

Related