5

I have a C function that must be callable from C and Python.

I'm having trouble figuring out how to pass a python list of c-type structs, each of which contains several nested structs, to the c function.

A single one of these structs looks like this in python:

class STATION_MM_NODE(ctypes.Structure):
    _fields_ = [
            ("signal", MM_STRUCT),
            ("noise", MM_STRUCT),
            ("signalWindowLen", ctypes.c_double),
            ("metadata", SAC_PZ)
    ]

And like this in C:

typedef struct stationMMnode {
    struct mantleMagStruct *signal;
    struct mantleMagStruct *noise;
    double signalWindowLen;
    SAC_PZ metadata;
} stationMMnode_t;

The c function that takes an array of stationMMnode structs is callable as:

double magnitudeCompute_Mw_Mm_Event(stationMMnode_t **stationMMarray, int numStations);

For instance, I can call it purely from C as in:

int testfunc() {
    stationMMnode_t *node1 = malloc(sizeof(struct stationMMnode));
    node1->signalWindowLen = 500;
    stationMMnode_t *node2 = malloc(sizeof(struct stationMMnode));
    node2->signalWindowLen = 100;

    struct stationMMnode *nodes[2];
    nodes[0] = node1;
    nodes[1] = node2;

    magnitudeCompute_Mw_Mm_Event(nodes, 2); // Works!
}

In python, I can create a list of nodes that looks similar to the c array of structs:

stationMMnodes = []
...
node = get_stationMMnode() # Returns a STATION_MM_NODE
node.signal = mm_signal
node.noise  = mm_noise
node.metadata = sacPoleZero
stationMMnodes.append(node)
...
wrap_lib.magnitudeCompute_Mw_Mm_Event(stationMMnodes, numStations) # Does NOT work

where I've defined the argtypes as:

wrap_lib.magnitudeCompute_Mw_Mm_Event.argtypes =
    [ctypes.POINTER(STATION_MM_NODE), ctypes.c_int ]

The model I'm using above (passing a ctype pointer to a c-style struct to a c function that takes a pointer to struct) seems to work fine when I am passing in a pointer to a single struct, however, for a pointer to an array of structs, it seems to break down. In addition, I am uncertain of what the python memory layout is for a list of structs versus an array of pointers to struct (as the C function is expecting).

Any help would be greatly appreciated!

Update: I found the following link very helpful: python ctypes array of structs

I solved my problem by: 1. Declaring an array of pointers to my struct:

nodeArrayType = ctypes.POINTER(STATION_MM_NODE) * 1024
nodeArray = nodeArrayType()
nstn = 0

2. Writing a C function to join the member structs into a larger struct (=a node) and return a pointer to that struct - which is stored in nodeArray[].

nodeArray[nstn] = wrap_libmth.libmth.makeNode(node.signal, node.noise, node.metadata)
nstn += 1

3. Fixing the argtype of the C function that receives the pointer to struct array:

wrap_libmth.libmth.magnitudeCompute_Mw_Mm_Event.argtypes = [ctypes.POINTER(ctypes.POINTER(STATION_MM_NODE)), ctypes.c_int]

So ... I have it working, but like most thing with Python, I feel like I'm holding the tiger by the tail as I don't fully understand exactly why it works and what (better) alternatives would be (e.g., the C hack makeNode() to return a pointer to a STATION_MM_NODE struct is less than satisfactory - it would be better to generate this struct fully in python).

2
  • I have done something similar. In my case though, the nested structs were declared as an actual array in the parent struct together with a length variable. In that case, the python glue code is trivial (just declare an array by multiplying the type with a scalar). Commented Apr 28, 2015 at 21:13
  • Update: I found the following post helpful: stackoverflow.com/questions/17101845/… Commented Apr 29, 2015 at 19:30

0

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.