Search code examples
pythoncctypes

How to use malloc and free with python ctypes?


I have a function in my C library, say runsim() which takes pointer to struct repdata as one of the arguments, where struct repdata is given by

struct repdata {
    int *var1;
    int *var2;
    int *var3;
    char *var4;
    double *var5;
    double *var6;
    int *var7;
};

When using C exclusively, I initialize a variable of type struct repdata calling the function,

struct repdata data;
void create_data_container(struct repdata *data, int len_data)
{

    data -> var1 = malloc( sizeof(int) * len_data );
    data -> var2 = malloc( sizeof(int) * len_data );
    data -> var3 = malloc( sizeof(int) * len_data );
    data -> var4 = malloc( sizeof(char) * len_data );
    data -> var5 = malloc( sizeof(double) * len_data);
    data -> var6 = malloc( sizeof(double) * len_data);
    data -> var7 = malloc( sizeof(int) * len_data);
}

and then fill this struct as simulation proceeds. After I have written the data to a file, I free the memory using the standard

free(data.var1);
free(data.var2);
.
.
.
free(data.var7);

I want to call the runsim() function from Python using Python Ctypes. For this I need to pass a pointer to a variable (that is equivalent to the type of struct repdata) as one of the runsim() arguments. Suppose that in Python I have defined an equivalent of struct repdata in the following manner.

import ctypes as C

class Repdata(C.Structure):
    _fields_ = [
        ("var1", C.POINTER(C.c_int)),
        ("var2", C.POINTER(C.c_int)),
        ("var3", C.POINTER(C.c_int)),
        ("var4", C.POINTER(C.c_char)),
        ("var5", C.POINTER(C.c_double)),
        ("var6", C.POINTER(C.c_double)),
        ("var7", C.POINTER(C.c_int)),
    ]

What is the equivalent of create_data_container function shown above on the Python side? I want to initialize an instance of Repdata which can be passed to C code and has sufficient memory for storing the replication data. And, once simulation is completed, how do I free the memory from Python?

I am using Ubuntu Linux 12.04.

Thanks in advance for your help.


Solution

  • You can allocate buffers using ctypes and assign them to the pointers. Once the Python ctypes objects have no references they will be freed automatically. Here's a simple example (with a Windows DLL...don't have a Linux machine handy, but the idea is the same) and a Python wrapper.

    create_string_buffer allocates a writable buffer that can be passed from Python to C that ctypes will marshal as a char*.

    You can also create writable arrays of ctypes types with the syntax:

    variable_name = (ctypes_type * length)(initial_values)
    

    test.c

    #include <stdio.h>
    #include <string.h>
    
    #ifdef _WIN32
    #   define API __declspec(dllexport)
    #else
    #   define API
    #endif
    
    typedef struct example {
        char* data;
        int len;          // of data buffer
        double* doubles;
        int count;        // of doubles
    } example;
    
    API void func(example* p) {
        // Put some example data in the buffers
        strcpy_s(p->data, p->len, "hello, world!");
        for(int i = 0; i < p->count; i++)
            p->doubles[i] = 1.1 * (i + 1);
    }
    

    test.py

    import ctypes as ct
    
    class Example(ct.Structure):
    
        _fields_ = (('data', ct.POINTER(ct.c_char)),
                    ('len', ct.c_int),
                    ('doubles', ct.POINTER(ct.c_double)),
                    ('count', ct.c_int))
    
        def __init__(self, length, count):
            self.data = ct.create_string_buffer(length)
            self.len = length
            self.doubles = (ct.c_double * count)()
            self.count = count
    
        def __repr__(self):
            '''Return string describing how to print an Example object.
            '''
            # Note that slicing a pointer to a specific
            # length returns a list of if its objects.
            return (f'Example({ct.string_at(self.data)}, {self.doubles[:self.count]}')
    
    class Dll:
    
        def __init__(self):
            self.dll = ct.CDLL('./test')
            self.dll.func.argtypes = ct.POINTER(Example),
            self.dll.func.restype = None
    
        def func(self, ex):
            self.dll.func(ct.byref(ex))
    
    d = Dll()
    e = Example(20, 5)
    print('before:', e)
    d.func(e)
    print('after:', e)
    

    Output:

    before: Example(b'', [0.0, 0.0, 0.0, 0.0, 0.0])
    after: Example(b'hello, world!', [1.1, 2.2, 3.3000000000000003, 4.4, 5.5])