pythoncpointersctypes

How can I pass a pointer to array of pointers to structs from python


I am using ctypes in Python and I need to pass a pointer to an array of pointers to structs to some C function. This is struct:

typedef struct {
    float x;
    float y;
    float z;
    float radius;
} Sphere;

And I have function with the following prototype:

void render(Sphere** spheres);

In Python I declared a class for the Sphere struct and I need to set argtypes to the render function:

lib_render = ctypes.cdll.LoadLibrary('librender.so')

class Sphere(ctypes.Structure):
    _fields_ = [('x', ctypes.c_float),
                ('y', ctypes.c_float),
                ('z', ctypes.c_float),
                ('radius', ctypes.c_float)]

render = lib_render.render
render.argtypes = [<cannot find out what needs to be here>]

spheres = numpy.array([Sphere(1, 2.8, 3, 0.5),
                       Sphere(4.2, 2, 1, 3.2)])
render(spheres)

How to pass that array correctly?


Solution

  • I don't use numpy much, but the following works without it. I am assuming if you are passing a pointer to pointers that the pointer list must be null-terminated.

    import ctypes as ct
    
    class Sphere(ct.Structure):
        _fields_ = (('x', ct.c_float),
                    ('y', ct.c_float),
                    ('z', ct.c_float),
                    ('radius', ct.c_float))
    
    dll = ct.CDLL('./test')
    dll.render.argtypes = ct.POINTER(ct.POINTER(Sphere)),
    dll.render.restype = None
    
    # Create a couple of objects
    a = Sphere(1, 2, 3, 4)
    b = Sphere(5, 6, 7, 8)
    
    # build a list of pointers, null-terminated.
    c = (ct.POINTER(Sphere) * 3)(ct.pointer(a), ct.pointer(b), None)
    dll.render(c)
    

    Test DLL:

    #include <stdio.h>
    
    typedef struct Sphere {
        float x;
        float y;
        float z;
        float radius;
    } Sphere;
    
    __declspec(dllexport) void render(Sphere** spheres) {
        for( ; *spheres; ++spheres)
            printf("%f %f %f %f\n", (*spheres)->x, (*spheres)->y,
                                    (*spheres)->z, (*spheres)->radius);
    }
    

    Output:

    1.000000 2.000000 3.000000 4.000000
    5.000000 6.000000 7.000000 8.000000
    

    With numpy, using void render(Sphere* spheres, size_t len), this works. Maybe someone more familiar with numpy can comment if Sphere** can be supported.

    import ctypes as ct
    import numpy as np
    
    class Sphere(ct.Structure):
        _fields_ = (('x', ct.c_float),
                    ('y', ct.c_float),
                    ('z', ct.c_float),
                    ('radius', ct.c_float))
    
    dll = ct.CDLL('./test')
    dll.render.argtypes = ct.POINTER(Sphere), ct.c_size_t
    dll.render.restype = None
    
    a = Sphere(1, 2, 3, 4)
    b = Sphere(5, 6, 7, 8)
    # c = (Sphere * 2)(a, b)
    # dll.render(c, len(c))
    d = np.array([a, b])
    dll.render(d.ctypes.data_as(ct.POINTER(Sphere)), len(d))