首页 > 解决方案 > Trouble using Python ctypes to run C dll function (array output with unknown size)

问题描述

I'm trying to execute a C function using Python ctypes, but I'm doing something wrong.

The C function was originally converted from MATLAB to C code using MATLAB coder. The function gives as output an array of undefined length, which depends on the input.

This is the MATLAB code:

function a = array_output(n)
%$codegen

if n > 2*pi
    a = 1:n;
else
    a = [1,2,3];
end

end

And this is the obtained C code:

void array_output(double n, emxArray_real_T *a)
{
  int i;
  int loop_ub;
  if (n > 6.2831853071795862) {
    i = a->size[0] * a->size[1];
    a->size[0] = 1;
    loop_ub = (int)floor(n - 1.0);
    a->size[1] = loop_ub + 1;
    emxEnsureCapacity_real_T(a, i);
    for (i = 0; i <= loop_ub; i++) {
      a->data[i] = (double)i + 1.0;
    }
  } else {
    i = a->size[0] * a->size[1];
    a->size[0] = 1;
    a->size[1] = 3;
    emxEnsureCapacity_real_T(a, i);
    a->data[0] = 1.0;
    a->data[1] = 2.0;
    a->data[2] = 3.0;
  }
}

struct emxArray_real_T
{
  double *data;
  int *size;
  int allocatedSize;
  int numDimensions;
  boolean_T canFreeData;
};

Please, find at the end of my question an example implementation they provide.

I am trying to execute it using Python's ctype using the following code:

dll = ctypes.cdll.LoadLibrary(dll_path)


class DataStruct(ctypes.Structure):
    _fields_ = [
        ('data', ctypes.POINTER(ctypes.c_double)),
        ('size', ctypes.POINTER(ctypes.c_int)),
        ('allocatedSize', ctypes.c_int),
        ('numDimensions', ctypes.c_int),
        ('canFreeData', ctypes.c_bool)
    ]


array = ctypes.POINTER(ctypes.c_double)()
size = numpy.array([0, 0]).ctypes.data_as(ctypes.POINTER(ctypes.c_int))
allocatedSize = 0
numDimensions = 2
canFreeData = True

data_struct = DataStruct(array, size, allocatedSize, numDimensions, canFreeData)

dll.array_output.argtypes = [ctypes.c_double, DataStruct]
dll.array_output.restype = None

dll.array_output(50, data_struct)

The output data_struct contains the right size field (data_struct.size[1] is 50), but when I try to access data_struct.data[0], I get the following error:

ValueError: NULL pointer access

Can anyone help me understanding what I'm doing wrong here?

--

Example implementation (code snippets):

void main(){
    // pseudo-code here
    emxArray_real_T *a;
    emxInitArray_real_T(&a, 2);
    
    /* Initialize function 'array_output' input arguments. */
    /* Call the entry-point 'array_output'. */
    array_output(5.0, a);
}

void emxInitArray_real_T(emxArray_real_T **pEmxArray, int numDimensions)
{
  emxInit_real_T(pEmxArray, numDimensions);
}

void emxInit_real_T(emxArray_real_T **pEmxArray, int numDimensions)
{
  emxArray_real_T *emxArray;
  int i;
  *pEmxArray = (emxArray_real_T *)malloc(sizeof(emxArray_real_T));
  emxArray = *pEmxArray;
  emxArray->data = (double *)NULL;
  emxArray->numDimensions = numDimensions;
  emxArray->size = (int *)malloc(sizeof(int) * numDimensions);
  emxArray->allocatedSize = 0;
  emxArray->canFreeData = true;
  for (i = 0; i < numDimensions; i++) {
    emxArray->size[i] = 0;
  }
}


void emxEnsureCapacity_real_T(emxArray_real_T *emxArray, int oldNumel)
{
  int newNumel;
  int i;
  void *newData;
  if (oldNumel < 0) {
    oldNumel = 0;
  }

  newNumel = 1;
  for (i = 0; i < emxArray->numDimensions; i++) {
    newNumel *= emxArray->size[i];
  }

  if (newNumel > emxArray->allocatedSize) {
    i = emxArray->allocatedSize;
    if (i < 16) {
      i = 16;
    }

    while (i < newNumel) {
      if (i > 1073741823) {
        i = MAX_int32_T;
      } else {
        i *= 2;
      }
    }

    newData = calloc((unsigned int)i, sizeof(double));
    if (emxArray->data != NULL) {
      memcpy(newData, emxArray->data, sizeof(double) * oldNumel);
      if (emxArray->canFreeData) {
        free(emxArray->data);
      }
    }

    emxArray->data = (double *)newData;
    emxArray->allocatedSize = i;
    emxArray->canFreeData = true;
  }
}

标签: pythonc++cmatlabctypes

解决方案


The second argument of array_output is a emxArray_real_T *, but you're trying to pass the structure by value as if it were just a emxArray_real_T. To fix that, change dll.array_output.argtypes = [ctypes.c_double, DataStruct] to dll.array_output.argtypes = [ctypes.c_double, ctypes.POINTER(DataStruct)] and dll.array_output(50, data_struct) to dll.array_output(50, ctypes.byref(data_struct)).


推荐阅读