Come si racchiude una funzione C che restituisce un puntatore a un array malloc'd con ctypes?

Aug 23 2020

Ho una funzione C che legge un file binario e restituisce un array di numeri interi senza segno di dimensioni dinamiche (la dimensione è basata sui metadati del file binario):

//example.c
#include <stdio.h>
#include <stdlib.h>

__declspec(dllexport)unsigned int *read_data(char *filename, size_t* array_size){
  FILE *f = fopen(filename, "rb");
  fread(array_size, sizeof(size_t), 1, f);
  unsigned int *array = (unsigned int *)malloc(*array_size * sizeof(unsigned int));
  fread(array, sizeof(unsigned int), *array_size, f);
  fclose(f);

  return array;
}

Questa risposta sembra dire che il modo corretto per passare l'array creato da C a Python è qualcosa del genere:

# example_wrap.py
from ctypes import *
import os

os.add_dll_directory(os.getcwd())
indexer_dll = CDLL("example.dll")

def read_data(filename):
    filename = bytes(filename, 'utf-8')
    size = c_size_t()
    ptr = indexer_dll.read_data(filename, byref(size))
    return ptr[:size]

Tuttavia, quando eseguo il wrapper python, il codice fallisce silenziosamente ptr[:size]come se stessi cercando di accedere a un array fuori dai limiti, e probabilmente lo sono, ma qual è il modo corretto per passare questo array di dimensioni dinamiche?

Risposte

2 filbranden Aug 23 2020 at 02:58

Alcune considerazioni:

Innanzitutto, è necessario impostare correttamente il prototipo della funzione C in modo che i ctypes possano convertirsi correttamente tra i tipi C e Python.

In secondo luogo, poiché sizeè effettivamente un ctypes.c_size_toggetto, è effettivamente necessario utilizzare size.valueper accedere al valore numerico della dimensione dell'array.

Terzo, poiché ptr[:size.value]copia effettivamente il contenuto dell'array in un elenco Python, ti consigliamo di assicurarti di avere anche free()l'array C allocato poiché non lo userai più.

(Forse copiare l'array in un elenco Python non è l'ideale qui, ma presumo che sia ok qui, poiché altrimenti hai più complessità nella gestione dell'array C in Python.)

Questo dovrebbe funzionare:

from ctypes import *
import os

os.add_dll_directory(os.getcwd())
indexer_dll = CDLL("example.dll")
indexer_dll.read_data.argtypes = [c_char_p, POINTER(c_size_t)
indexer_dll.read_data.restype = POINTER(c_int)
libc = cdll.msvcrt

def read_data(filename):
    filename = bytes(filename, 'utf-8')
    size = c_size_t()
    ptr = indexer_dll.read_data(filename, byref(size))
    result = ptr[:size.value]
    libc.free(ptr)
    return result