Как динамически распределить массив и назначить адрес указателю, определенному в разделяемой библиотеке, используя ctypes? - PullRequest
0 голосов
/ 27 апреля 2020

Моя библиотека создана из simulink и выглядит следующим образом:

lib.h

extern double *params1;
extern double *params2; 

typedef struct {
  unsigned int params1_len;
  unsigned int params2_len;
} P_model_T;

lib. c

double *params1;
double *params2;

P_model_T model_params = { 0, 0 };

void step(double in, double *out) {
  // some code using the parameter arrays
}

Я намерен использовать его с python следующим образом, предполагая, что set_params всегда будет вызываться до вызова функции step (таким образом, инициализируя необходимые массивы параметров):

lib_wrapper.py

from ctypes import *

import numpy as np

class LibWrapper:
  def __init__(self):
    self._library = CDLL("lib.dll")
    self._param_struct = ModelParameters.in_dll(self._library, 'model_params')

  def step(self, _in):
    out = c_double()
    self._library.step(c_double(_in), byref(out))
    return out

  def get_params(self, param_name):
    array_len = getattr(self._param_struct, f'{param_name}_len')  # get length of parameter array
    array_p = POINTER(c_double * array_len).in_dll(self._library, param_name)  # get actual array from library
    return np.array(array_p.contents)  # copy and return

  def set_params(self, param_name, data):
    array = (c_double * len(data))(*data)
    setattr(self._library, param_name, pointer(array))

class ModelParameters(Structure):
    _fields_ = [('params1_len', c_uint32),
                ('params2_len', c_uint32)]

Мой вопрос касается реализации set_params: насколько я понимаю, мое решение не присваивает адрес array моему фактическому params1 / params2 переменная в dll, но она изменяет только представление dll внутри ctypes. Из-за этого я всегда получаю нарушение прав доступа при вызове моей функции step, поскольку мои переменные параметров по-прежнему указывают на 0x0000000000000000.

Как правильно назначить выделенный массив для params переменных в общей библиотеке?

Последующие действия: будет ли более чистый способ написать мою get_params? Может ли ctypes каким-то образом «запомнить» тип моих переменных params после того, как я их установил, чтобы мне не нужно было приводить их?

1 Ответ

0 голосов
/ 28 апреля 2020

Я заставил его работать, но это был трюк. Ваш set_params не просматривал параметр на in_dll, поэтому он не был эффективен. Кроме того, даже при in_dll доступе к содержимому просто пытался разыменовать нулевой указатель, поэтому мне нужно было получить адрес параметра, чтобы изменить нулевой указатель. Для проверки использовались следующие библиотеки DLL и Python:

test. c

#define API __declspec(dllexport)

typedef struct {
  unsigned int params1_len;
  unsigned int params2_len;
} P_model_T;

API double *params1;
API double *params2;

API P_model_T model_params = { 0, 0 };

// For testing:
//   Increment params1 array by the "in" parameter.
//   Return the sum of the params2 array.
//
API void step(double in, double *out) {
    for(unsigned int i = 0; i < model_params.params1_len; ++i)
        params1[i] += in;
    *out = 0.0;
    for(unsigned int i = 0; i < model_params.params2_len; ++i)
        *out += params2[i];
}

test.py

from ctypes import *

import numpy as np

class LibWrapper:
    def __init__(self):
        self._library = CDLL('test.dll')
        self._param_struct = ModelParameters.in_dll(self._library, 'model_params')
        self._library.step.argtypes = c_double,POINTER(c_double)
        self._library.step.restype = None

    def step(self, in_):
        out = c_double()
        self._library.step(in_, byref(out))
        return out.value

    def get_params(self, param_name):
        array_len = getattr(self._param_struct, f'{param_name}_len')  # get length of parameter array
        array_p = POINTER(c_double * array_len).in_dll(self._library, param_name)  # get actual array from library
        return np.array(array_p.contents)  # copy and return

    def set_params(self, param_name, data):
        array = (c_double * len(data))(*data)
        setattr(self,f'_{param_name}',array) # keep a reference since "array" will will free after return otherwise.
        array_p = POINTER(c_double).in_dll(self._library, param_name)  # get actual array from library
        array_pp = POINTER(POINTER(c_double)).from_address(addressof(array_p)) # get address of array pointer
        array_pp.contents = array # point it to the array
        setattr(self._param_struct, f'{param_name}_len', len(data)) # Update the parameter length

class ModelParameters(Structure):
    _fields_ = [('params1_len', c_int),
                ('params2_len', c_int)]

lib = LibWrapper()
lib.set_params('params1',[1.0,2.0,3.0])
lib.set_params('params2',[3.0,4.0,5.0])
print(lib.step(5))
print(lib.get_params('params1'))
print(lib.get_params('params2'))

Вывод:

12.0
[6. 7. 8.]
[3. 4. 5.]
...