ctypes struct containing arrays and manipulating

Question:

I’m trying to use ctypes in my python code test.pyto pass the array to test.c and do some calculation and bring the result to python code.

Here is ‘test.c’

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "test.h"

void test(int N, complex *v){
    int k;
    for(k=0; k<N; k++){
        v[k].Re = v[k].Re + 1;
        v[k].Im = v[k].Im + 1;    
        printf("%d %f %fn", k, v[k].Re, v[k].Im);
    }
}

v is the structed array defined in test.h

typedef struct{float Re; float Im;} complex;

From Python I can do something like this

import numpy as np
from ctypes import cdll
from ctypes import *

class complex(Structure):
    _fields_ = (('Re', POINTER(c_float)),
                ('Im', POINTER(c_float)))

    def __init__(self, I, Q, lenIQ):
        array_type = c_float * lenIQ
        self.Re = array_type(*I)
        self.Im = array_type(*Q)

    def __repr__(self):
        return f'Complex({self.Re}, {self.Im})'

cgmap = cdll.LoadLibrary('./test.dll')
test = cgmap.test
test.argtypes = (c_int, complex)
I = [2,2,3,4]
Q = [5,6,7,8]
lenIQ = len(I)

myarray = complex(I, Q, lenIQ)
test(c_int(lenIQ), myarray)
print(myarray)
print(myarray[0])

But when I execute test.py, then :

0 4284182633119744.000000 1.000000
1 4585659272527872.000000 1.000000
2 3.983704 1.000000
3 3.983746 1.000000
Complex(<__main__.LP_c_float object at 0x0000029959825A48>, <__main__.LP_c_float object at 0x0000029959825A48>)
Traceback (most recent call last):
  File "test.py", line 28, in <module>
    print(myarray[0])
TypeError: 'complex' object does not support indexing

Is there anyway to solve without modifying test.c? Any help would be appreciated 😉

Asked By: Dana Kim

||

Answers:

you are mixing up between creating a struct, creating an array of struct, and creating a pointer to an array of struct, those are 3 different operations, i have commented the code for clarity.

import numpy as np
from ctypes import cdll
from ctypes import *

class complex(Structure):  # struct with two floats
    _fields_ = (('Re', c_float),
                ('Im', c_float))

    def __init__(self, I_val, Q_val):
        self.Re = I_val
        self.Im = Q_val

    def __repr__(self):
        return f'Complex({self.Re}, {self.Im})'

I = [2,2,3,4]
Q = [5,6,7,8]
lenIQ = len(I)
my_array_type = complex*lenIQ  # create the type of array (complex[])
my_array = my_array_type()  # create the array (complex my_array[4])

for i in range(len(I)):
    my_array[i] = complex(I[i],Q[i])  # fill each individual object in the array

# next line is equivalent to "complex* pointer_to_array = (complex*)(my_array);"
pointer_to_array = cast(my_array, POINTER(complex))  # create the pointer to the first element of the array (complex*)

print(my_array)  # prints type of my_array
print(my_array[0])  # equivalent to c's my_array[0]
print(pointer_to_array[0])  # equivalent to c's pointer_to_array[0]
<__main__.complex_Array_4 object at 0x000002195A9B8C40>
Complex(2.0, 5.0)
Complex(2.0, 5.0)

now for calling

cgmap = cdll.LoadLibrary('./test.dll')
test = cgmap.test
test.argtypes = (c_int, POINTER(complex))
test.restype  = None
test(c_int(lenIQ), pointer_to_array)
Answered By: Ahmed AEK

Make sure your types match. The structure contains c_float not POINTER(c_float) to match the C definition. The .argtypes are c_int and POINTER(Complex).

ctypes can initialize the structure and arrays without a lot of extra work:

test.c

#include <stdio.h>

#ifdef _WIN32
#   define API __declspec(dllexport)
#else
#   define API
#endif

typedef struct { float Re; float Im; } complex;

API void test(int N, complex *v) {
    int k;
    for(k = 0; k < N; ++k) {
        v[k].Re = v[k].Re + 1;
        v[k].Im = v[k].Im + 1;
        printf("%d %f %fn", k, v[k].Re, v[k].Im);
    }
}

test.py

import ctypes as ct

class Complex(ct.Structure):
    _fields_ = (('Re', ct.c_float),  # not POINTER(c_float)
                ('Im', ct.c_float))

    def __repr__(self):
        return f'Complex({self.Re:g}{self.Im:+g}j)'

    @staticmethod
    def array(reals, imags):
        '''Helper method to create Complex arrays.
        '''
        return (Complex * len(reals))(*zip(reals, imags))

I = [2,2,3,4]
Q = [5,6,7,8]

arr = Complex.array(I, Q)
print(list(arr)) # easy way to display the contents when __repr__ is defined.

dll = ct.CDLL('./test')
dll.test.argtypes = ct.c_int, ct.POINTER(Complex)
dll.test.restype = None

dll.test(len(arr), arr)

Output:

[Complex(2+5j), Complex(2+6j), Complex(3+7j), Complex(4+8j)]
0 3.000000 6.000000
1 3.000000 7.000000
2 4.000000 8.000000
3 5.000000 9.000000
Answered By: Mark Tolonen
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.