0

我的库是从 simulink 生成的,如下所示:

库文件

extern double *params1;
extern double *params2; 

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

库文件

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地址分配给 dll 中的实际params1/params2变量,但它只会更改 ctypes 中的 dll 表示。因此,在调用我的step函数时,我总是会遇到访问冲突,因为我的参数变量仍然指向 0x0000000000000000。

将分配的数组分配给params共享库中的变量的正确方法是什么?

跟进:有没有更干净的方式来写 my get_params?ctypes 可以params在我设置变量后以某种方式“记住”我的变量的类型,这样我就不需要强制转换它们了吗?

4

1 回答 1

0

我让它工作,但这是一个技巧。您set_params没有查找参数,in_dll因此它无效。此外,即使in_dll访问内容只是试图取消引用已经存在的空指针,所以我需要获取参数的地址来更改空指针。我使用以下 DLL 和 Python 代码进行测试:

测试.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];
}

测试.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.]
于 2020-04-27T22:25:41.327 回答