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).