简体   繁体   中英

How to pass arrays with ctypes for NI VeriStand shared library

I have a Simulink model that I need to execute from within Python. I have been using C code generated with NI VeriStand to compile a Linux shared library that allows me to execute my simulation from within Python.

One of the things I want to do is save the state of my simulation (ie, continuous and discrete variables, and clock ticks). The C source exported by VeriStand provides a function called NIRT_GetSimState for such a purpose.

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;
}

I have been trying to find a way to use this function in Python, as loaded from a shared library.

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

I want to find a way to pass the correct data types to the function in Python. From what I understand, I need to pass pointers to integers and to arrays.

I am using the following function for testing. I am creating variables and arrays using 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]

I seem to be getting the right values for the number of discrete and continuous states but the arrays with the continuous states and their names are not updated. This is what the function prints:

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]

So, we can see that the function call did not update the arrays. I think that I'm probably not using the right data types to call the function. This is my first time using ctypes.

Could someone confirm if there is a mistake in the data types? and what the correct syntax should be?

Thank you.

Check out [Python 3]: ctypes - A foreign function library for Python .

There are a couple of things wrong with the Python code:

Also the way the function is designed is poor:

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)
  • Way too many arguments. This case would call for a container ( struct ) to encapsulate them
  • It doesn't seem to check the pointers (1 st for nullptr ), then only dumps the data to their addresses, never wondering whether if it went out of bounds. That means that the function caller must allocate enough space for all buffers, in order for the info to fit in. Usually, this kind of situation is handled:
    • Via another argument that specifies the buffer size, and if the function has more data to place in the buffer, it either doesn't do anything either fills the buffer, and at the end returns an error
    • The functions allocates the memory (it's the caller responsibility to deallocate it)
  • I see that for many fields an array of length 1 is declared. If only one element is supposed to be returned then don't make it an array ( (ctypes.c_double * 1)(-999.99) -> ctypes.c_double(-999.99) ). Anyway, I didn't change it

Based on the function body, here's how you could get it working (this is one way - needless to say that I didn't test the code):

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

It turns out that the mistake was not on the Python code but on the way I was calling the C code. The function was meant to be called a first time to get the lengths of the arrays to allocate, and a second time to copy the variables into the arrays.

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM