comment passer un tableau float * à la méthode C à partir d'un script python dont l'allocation de mémoire a lieu dans la couche C

Oct 12 2020

J'essaie d'appeler des méthodes C à partir d'un script Python, la méthode C appelle la méthode C ++. J'alloue un tableau dans la méthode getResults () en utilisant malloc (). Maintenant, le problème est de savoir comment passer les arguments à float * oresults dans un script python dont l'allocation de mémoire a lieu à l'intérieur de la couche C. C'est io.c

int getResults(char* iFilename, char* iStagename, int iStateidCnt, 
    int* Stateids, int iEntityIdCount, int* iEntityids, char* iEntityType,
    char* iVariablegroup, char* ivariable, int *oRescount,
    float* oResults)
{
    int Status, i;
        EString etype(iEntityType), stagename(iStagename);
    EString vargroup(iVariablegroup);
    std::vector<ERF_INT> entity_ids;
    std::vector<ERF_INT> stateids;
    std::vector<ERF_FLOAT> results;
    _CopyIntArrayIntoVector(iStateidCnt, Stateids, stateids);
    _CopyIntArrayIntoVector(iEntityIdCount, iEntityids, entity_ids);
    CreateIoInstance(iFilename, iStagename);
    ioData pIodata = CreateIoDataInstance();
    if (iEntityIdCount <= 0)
        pIodata.setWholeSection(true);
    else
    {
        pIodata.setWholeSection(false);
        pIodata.setEntityList(entity_ids);
    }
        
    pIodata.setStateList(stateids);
    pIodata.setType(etype);
    pIodata.setVariableGroup(iVariablegroup);
    pIodata.setVariable(ivariable);
        //This is C++ method
    Status = pIo->get_results(pIodata, results);
    *oRescount = results.size();
        //allocation for oresults whose size > 2
    oResults = (float*)malloc(results.size() * sizeof(float));
    _CopyVectorIntoDoubleArray(results, oResults);
    return Status;
}

test.py

from ctypes import *
import os, sys
dll = CDLL('D:\\erf_utils_python\\erf_utils_io.dll')
dll.getresults.argtypes = (c_char_p,c_char_p,c_int,POINTER(c_int),c_int,POINTER(c_int),c_char_p,
                                  c_char_p,c_char_p,POINTER(c_int),POINTER(c_float))
dll.getresults.restype = c_int


def make_array(ctype,arr):
    return len(arr),(ctype * len(arr))(*arr)

def getresults(filename,stagename,sids,eids,entitytype,groups,variables):
    if(len(sids)>0):
       stateidcount,stateids = make_array(c_int,sids)
    if(len(eids)>0):
       entityidcount,entityid = make_array(c_int,eids)
    oresultlen = c_int()
    float_values = POINTER(c_float)
    err = dll.getresults(filename,stagename,stateidcount,stateids,entityidcount,entityid,
                                entitytype,groups,variables,byref(oresultlen), byref(float_values))
    return err,oresultlen.value, float_values

filename = b'D:\\inputfile.h5'
stagename = b"post"
stateids = [2]
stateidcount = 1
entityidcount = 1
entityid = [1]
entitytype = b"test"
variablecount = 1
variablegroup = b"testdata"
variable = b"next"

err,oreslen,ores = getresults(filename,stagename,stateids,entityid,entitytype,variablegroup,variable)

TypeError: l'argument byref () doit être une instance de ctypes, pas '_ctypes.PyCPointerType', c'est l'erreur que j'obtiens lorsque j'exécute le script. Je ne sais pas trop comment envoyer l'argument pour float * oresults dans le script.

Réponses

MarkTolonen Oct 12 2020 at 06:48

Le float* oResultsparamètre est passé par valeur, il est donc impossible de renvoyer un pointeur alloué dans ce paramètre. Au lieu de cela, utilisez un float** oResults.

Il float_values = POINTER(c_float)s'agit également d'un type et non d'une instance d'un type. Donc byref(float_values)équivaut au C invalide &(float*). , Vous voulez plutôt une instance d'un pointeur POINTER(c_float)()( entre parenthèses les notes) et passer que par référence, similaire à C float *p; func(&p). Cela passera le pointeur par adresse à la fonction C, qui peut ensuite le modifier en tant que paramètre de sortie.

Voici un exemple simplifié se concentrant uniquement sur les paramètres int *oRescountet float** oResults. Une fonction pour libérer l'allocation est également nécessaire:

test.cpp

#include <vector>
#define API __declspec(dllexport)

extern "C" {
    API int getResults(size_t *oRescount, float** oResults) {
        std::vector<float> results {1.25,2.5,3.75,5.0}; // Simulated results
        *oRescount = results.size(); // Return size of results
        auto tmp = new float[results.size()]; // allocate
        for(size_t i = 0; i < results.size(); ++i) // copy vector to allocation
            tmp[i] = results[i];
        *oResults = tmp; // return allocation
        return 0;
    }

    API void freeResults(float* oResults) {
        delete [] oResults;
    }
}

test.py

from ctypes import *
dll = CDLL('./test')
dll.getResults.argtypes = POINTER(c_size_t),POINTER(POINTER(c_float))
dll.getResults.restype = c_int

def getresults():
    oRescount = c_size_t()         # instance to hold the returned size
    oResults = POINTER(c_float)()  # instance of a float* to hold the returned allocation.
    err = dll.getResults(byref(oRescount), byref(oResults))

    # oResults is a float* and it is possible to index past the end.
    # Make a copy into a Python list slicing to the correct size,
    # then free it so there is no memory leak.
    results = oResults[:oRescount.value]
    dll.freeResults(oResults)

    return err,results

err,ores = getresults()
print(err,ores)

Production:

0 [1.25, 2.5, 3.75, 5.0]
1 PascalGetreuer Oct 12 2020 at 06:37

Dans le code C ++, la signature int getResults(..., float* oResults)n'est pas en mesure de communiquer le pointeur alloué à l'appelant. La ligne

oResults = (float*)malloc(results.size() * sizeof(float));

définit le oResultspointeur localement dans getResults, sans affecter l'appelant. Pour sortir un pointeur, vous devez soit returnou utiliser un pointeur à pointer argument: int getResults(..., float** oResults).

Dans le code Python, je ne suis pas familier avec les ctypes mais cela semble float_values = POINTER(c_float)poser un problème. POINTER(c_float)crée un type Python pour un pointeur sur float. Vous voudriez POINTER(c_float)()créer une instance d'un tel pointeur (qui est initialement nul).

documentation ctypes sur les pointeurs: https://docs.python.org/3/library/ctypes.html#pointers