【问题标题】:How to pass arrays with ctypes for NI VeriStand shared library如何为 NI VeriStand 共享库传递带有 ctypes 的数组
【发布时间】:2019-04-11 19:04:25
【问题描述】:

我有一个需要在 Python 中执行的 Simulink 模型。我一直在使用 NI VeriStand 生成的 C 代码来编译一个 Linux 共享库,它允许我在 Python 中执行我的模拟。

我想做的一件事是保存我的模拟状态(即连续和离散变量,以及时钟滴答声)。 VeriStand 导出的 C 源代码为此提供了一个名为 NIRT_GetSimState 的函数。

DLL_EXPORT int32_t NIRT_GetSimState(int32_t* numContStates, char  * contStatesNames, double* contStates, int32_t* numDiscStates, char
  * discStatesNames, double* discStates, int32_t* numClockTicks, char
  * clockTicksNames, int32_t* clockTicks)
{
  int32_t count = 0;
  int32_t idx = 0;
  if ((numContStates != NULL) && (numDiscStates != NULL) && (numClockTicks !=
       NULL)) {
    if (*numContStates < 0 || *numDiscStates < 0 || *numClockTicks < 0) {
      *numContStates = 1;
      *numDiscStates = 0;
      *numClockTicks = NUMST - TID01EQ;
      return NI_OK;
    }
  }

  if ((contStates != NULL) && (contStatesNames != NULL)) {
    idx = 0;
    contStates[idx] = NIRT_GetValueByDataType(&(electric_motor_X.speed), 0, 0, 0);
    strcpy(contStatesNames + (idx++ * 100), "speed");
  }

  if ((clockTicks != NULL) && (clockTicksNames != NULL)) {
    clockTicks[0] = S->Timing.clockTick0;
    strcpy(clockTicksNames, "clockTick0");
  }

  UNUSED_PARAMETER(count);
  UNUSED_PARAMETER(idx);
  return NI_OK;
}

我一直在尝试找到一种在 Python 中使用此函数的方法,就像从共享库中加载的那样。

from ctypes import *
self._model = CDLL(model_lib)
self._lib_get_state = self._model.NIRT_GetSimState

我想找到一种方法将正确的数据类型传递给 Python 中的函数。据我了解,我需要将指针传递给整数和数组。

我正在使用以下功能进行测试。我正在使用 ctypes 创建变量和数组。

def _get_state(self):
    numContStates = c_int(-999)
    contStatesNames = (c_wchar_p*1)('a')
    contStates = (c_double*1)(-999.99)
    numDiscStates = c_int(-999)
    discStatesNames = (c_wchar_p*1)('a')
    discStates = (c_double*1)(-999.99)
    numClockTicks = c_int(-999)
    clockTicksNames = (c_wchar_p*1)('a')
    clockTicks = (c_int*1)(-999)
    self._lib_get_state(byref(numContStates), byref(contStatesNames), byref(contStates), byref(numDiscStates), byref(discStatesNames),
        byref(discStates), byref(numClockTicks), byref(clockTicksNames), byref(clockTicks))
    print('Number of continuous states: ', numContStates.value)
    print('Number of discrete states: ', numDiscStates.value)
    print('Number of clock ticks: ', numClockTicks.value)
    print('Names of continuous states: ', list(contStatesNames)) # Expecting ['speed']
    print('Values of continuous states: ', list(contStates)) # Expecting [0.0]

我似乎得到了离散和连续状态数量的正确值,但是具有连续状态及其名称的数组没有更新。这是函数打印的内容:

Number of continuous states:  1
Number of discrete states:  0
Number of clock ticks:  1
Names of continuous states:  ['a']
Values of continuous states:  [-999.99]

所以,我们可以看到函数调用没有更新数组。我认为我可能没有使用正确的数据类型来调用该函数。这是我第一次使用 ctypes。

有人可以确认数据类型是否有错误吗?正确的语法应该是什么?

谢谢。

【问题讨论】:

    标签: python c arrays ctypes


    【解决方案1】:

    查看[Python 3]: ctypes - A foreign function library for Python

    Python 代码有几个问题:

    另外函数设计的方式很差:

    DLL_EXPORT int32_t NIRT_GetSimState(int32_t *numContStates, char *contStatesNames,
                                        double *contStates, int32_t *numDiscStates,
                                        char *discStatesNames, double *discStates,
                                        int32_t *numClockTicks, char *clockTicksNames,
                                        int32_t *clockTicks)
    
    • 参数太多了。这种情况需要一个容器(struct)来封装它们
    • 它似乎没有检查指针(1st for nullptr),然后只将数据转储到它们的地址,从不怀疑它是否超出界限。这意味着函数调用者必须为所有缓冲区分配足够的空间,以使信息适合。通常,处理这种情况:
      • 通过另一个参数指定缓冲区大小,如果函数有更多数据要放入缓冲区,它要么不执行任何操作要么填充缓冲区,最后返回错误
      • 函数分配内存(释放内存是调用者的责任)
    • 我看到,对于许多字段,声明了一个长度为 1 的数组。如果只返回一个元素,则不要将其设为数组 ((ctypes.c_double * 1)(-999.99) -> ctypes.c_double(-999.99))。反正我没改过

    根据函数体,您可以通过以下方式使其工作(这是一种方法 - 不用说我没有测试代码):

    self._lib_get_state.argtypes = [
        ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_char),
        ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_int32),
        ctypes.POINTER(ctypes.c_char), ctypes.POINTER(ctypes.c_double),
        ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_char),
        ctypes.POINTER(ctypes.c_int32),
    ]
    self._lib_get_state.restype = ctypes.c_int32
    
    numContStates = ctypes.c_int32(-999)
    contStatesNames = ctypes.create_string_buffer(106)  # The "speed" text is copied 100 characters from beginning
    contStates = ctypes.c_double(-999.99)
    numDiscStates = ctypes.c_int32(-999)
    discStatesNames = (ctypes.c_char * 1)(b"a")
    discStates = (ctypes.c_double * 1)(-999.99)
    numClockTicks = ctypes.c_int32(-999)
    clockTicksNames = ctypes.create_string_buffer(11)  # Equivalent to: ctypes.c_char * 11
    clockTicks = (ctypes.c_int32 * 1)(-999)
    
    result = self._lib_get_state(
        ctypes.byref(numContStates), ctypes.cast(contStatesNames, ctypes.POINTER(ctypes.c_char)),
        ctypes.byref(contStates), ctypes.byref(numDiscStates),
        ctypes.cast(discStatesNames, ctypes.POINTER(ctypes.c_char)), ctypes.cast(discStates, ctypes.POINTER(ctypes.c_double)),
        ctypes.byref(numClockTicks), ctypes.cast(clockTicksNames, ctypes.POINTER(ctypes.c_char)),
        ctypes.byref(clockTicks))
    
    
    clockTicksNames.value  # This is how to get the string out
    

    【讨论】:

    • 我尝试了建议的更正,但代码仍然不起作用。我无法更改 C 代码,因为它是由 NI VeriStand 代码生成器自动生成的。我按照指示声明了 argtypes 和 restype。我无法使用数据类型 c_char 测试代码,因为名称数组是字符串数组(不是 char)。代码为 c_char_p 运行,但 C 函数不更新数组。
    【解决方案2】:

    事实证明,错误不在 Python 代码上,而是在我调用 C 代码的方式上。第一次调用该函数是为了获取要分配的数组的长度,第二次是为了将变量复制到数组中。

    self._lib_get_state = self._model.NIRT_GetSimState
    
    self.num_cont_states = c_int(-999)
    self.num_disc_states = c_int(-999)
    self.num_clock_ticks = c_int(-999)
    
    self._lib_get_state(byref(self.num_cont_states), byref(c_char()), byref(c_double()),
        byref(self.num_disc_states), byref(c_char()), byref(c_double()),
        byref(self.num_clock_ticks), byref(c_char()), byref(c_int()))
    
    self._cont_states_names = create_string_buffer(b'\000' * (100*self.num_cont_states.value))
    self._cont_states = (c_double*self.num_cont_states.value)()
    self._disc_states_names = create_string_buffer(b'\000' * (100*self.num_disc_states.value))
    self._disc_states = (c_double*self.num_disc_states.value)()
    self._clock_ticks_names = create_string_buffer(b'\000' * (100*self.num_clock_ticks.value))
    self._clock_ticks = (c_int*self.num_clock_ticks.value)()
    
    self._lib_get_state(byref(self.num_cont_states), self._cont_states_names,
        self._cont_states, byref(self.num_disc_states), self._disc_states_names,
        self._disc_states, byref(self.num_clock_ticks), self._clock_ticks_names,
        self._clock_ticks)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-02-10
      • 1970-01-01
      • 1970-01-01
      • 2012-07-02
      • 1970-01-01
      • 2014-12-04
      • 1970-01-01
      相关资源
      最近更新 更多