¿Cómo usar malloc y gratis con python ctypes?

4 minutos de lectura

Avatar de usuario de Curious2learn
curioso2aprender

Tengo una función en mi biblioteca C, digamos runsim() que lleva el puntero a struct repdata como uno de los argumentos, donde struct repdata es dado por

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

Cuando uso C exclusivamente, inicializo una variable de tipo struct repdata llamando a la función,

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);
}

y luego complete esta estructura a medida que avanza la simulación. Después de haber escrito los datos en un archivo, libero la memoria usando el estándar

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

quiero llamar al runsim() función de Python usando Python Ctypes. Para esto necesito pasar un puntero a una variable (que es equivalente al tipo de struct repdata) como uno de los runsim() argumentos Supongamos que en Python he definido un equivalente de struct repdata en la siguiente manera.

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)),
    ]

¿Cuál es el equivalente de create_data_container función que se muestra arriba en el lado de Python? Quiero inicializar una instancia de Repdata que se pueda pasar al código C y tenga suficiente memoria para almacenar los datos de replicación. Y, una vez que se completa la simulación, ¿cómo libero la memoria de Python?

Estoy usando Ubuntu Linux 12.04.

Gracias de antemano por tu ayuda.

Avatar de usuario de Mark Tolonen
marca tolonen

Puede asignar búferes usando ctypes y asignarlos a los punteros. Una vez que los objetos ctypes de Python no tengan referencias, se liberarán automáticamente. Aquí hay un ejemplo simple (con una DLL de Windows… no tengo una máquina Linux a mano, pero la idea es la misma) y un envoltorio de Python.

create_string_buffer asigna un búfer grabable que se puede pasar de Python a C que ctypes se organizará como un char*.

También puede crear matrices escribibles de ctypes tipos con la sintaxis:

variable_name = (ctypes_type * length)(initial_values)

xh

#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif

struct example {
    char* data;
    int len;          // of data buffer
    double* doubles;
    int count;        // of doubles
};

DLL_API void func(struct example* p);

xc

#include <stdio.h>
#define DLL_EXPORTS
#include "x.h"

void func(struct example* p)
{
    int i;
    strcpy_s(p->data,p->len,"hello, world!");
    for(i = 0; i < p->count; i++)
        p->doubles[i] = 1.1 * (i + 1);
}

x.py

import ctypes

class Example(ctypes.Structure):

    _fields_ = [
        ('data',ctypes.POINTER(ctypes.c_char)),
        ('len',ctypes.c_int),
        ('doubles',ctypes.POINTER(ctypes.c_double)),
        ('count',ctypes.c_int)]

    def __init__(self,length,count):
        self.data = ctypes.cast(ctypes.create_string_buffer(length),ctypes.POINTER(ctypes.c_char))
        self.len = length
        self.doubles = (ctypes.c_double * count)()
        self.count = count

    def __repr__(self):
        return 'Example({},[{}])'.format(
            ctypes.string_at(self.data),
            ','.join(str(self.doubles[i]) for i in range(self.count)))

class Dll:

    def __init__(self):
        self.dll = ctypes.CDLL('x')
        self.dll.func.argtypes = [ctypes.POINTER(Example)]
        self.dll.func.restype = None

    def func(self,ex):
        self.dll.func(ctypes.byref(ex))

d = Dll()
e = Example(20,5)
print('before:',e)
d.func(e)
print ('after:',e)

Producción

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

  • Puedo estar equivocado, pero esto parece ser diferente de lo que estoy buscando. En mi problema, los datos se generan en el código C y quiero pasar un contenedor al código C de Python que se puede llenar con esos datos. En la respuesta aquí, los datos se pasan de Python a C.

    – Curioso2aprender

    8 de septiembre de 2013 a las 3:42


  • @ Curious2learn, los búferes están asignados pero no tienen que inicializarse en el lado de Python. También puede completarlos en el lado C y los datos aparecerán en el lado de Python. Cambiaré lo anterior para demostrar eso en su lugar.

    –Mark Tolonen

    8 de septiembre de 2013 a las 5:11

  • @MarkTolonen Encontrando su respuesta: ¿podría “inyectar” create_string_buffer como un malloc reemplazo en mi c-biblioteca a través de CFUNCTYPE(c_void_p, c_size_t)(create_string_buffer)? Me gustaría evitar “preguntarle” a mi biblioteca c cuánta memoria necesita todo el tiempo…

    – sebastián

    14/11/2014 a las 15:20


¿Ha sido útil esta solución?