Search code examples
pythonmatlabcharstructurectypes

Using Python and ctypes to pass a variable length string inside a structure to c function (generated by Matlab Coder)


I am trying to pass a variable size string inside a structure from Python into C code (or rather into a DLL/SO). The C-code is generated by Matlab Coder, so I am fairly limited to changing the C-code (although some tweaks can be made in Matlab to change the output).

I use ctypes in Python to creating structures with various elements and that works, and so does passing variables with strings of varying length. But, I can't get a varying length string inside a structure to work.

Passing a string works well when create a string input in Matlab the generated C-code. I pass the string via ctypes into the C type definitions as follows:

Matlab generated C-code:

/* Function Declarations */
extern double test_string_func(const char apa_vl_data[],
                               const int apa_vl_size[2]);

Python:

str2_in = "hyja"

bb = ct.c_char_p(str2_in.encode('utf-8'))
bb_sz = np.array([1, len(str2_in)]).astype(dtype=int)
bb_sz_p = bb_sz.ctypes.data

Initialise and make the call:

libC.test_string_func.argtypes = [ct.c_char_p, np.ctypeslib.ndpointer()]
dbl_out = libC.test_string_func(bb, bb_sz)

BUT, when I create a struct in Matlab (C and Python) I can't get the call to work:

C-code structure definitions:

/* Type Definitions */
#ifndef struct_emxArray_char_T_1x10
#define struct_emxArray_char_T_1x10
struct emxArray_char_T_1x10 {
  char data[10];
  int size[2];
};
#endif /* struct_emxArray_char_T_1x10 */
#ifndef typedef_emxArray_char_T_1x10
#define typedef_emxArray_char_T_1x10
typedef struct emxArray_char_T_1x10 emxArray_char_T_1x10;
#endif /* typedef_emxArray_char_T_1x10 */

#ifndef typedef_struct0_T
#define typedef_struct0_T
typedef struct {
  double val;
  emxArray_char_T_1x10 str;
} struct0_T;
#endif /* typedef_struct0_T */

from the header file:

extern double test_string_func(const struct0_T *apa_struct);

One approach of many that I have tried in Python:

class emxArray_char_T_1x10(ct.Structure):
    """ creates a struct to match emxArray_char_T_1x10

     C-code from Matlab:
     /* Type Definitions */
        #ifndef struct_emxArray_char_T_1x10
        #define struct_emxArray_char_T_1x10
        struct emxArray_char_T_1x10 {
        char data[10];
        int size[2];
        }; """

    _fields_ = [('data', ct.c_char_p),
                ('size', ct.POINTER(ct.c_int))]


class testInStruct(ct.Structure):
    """creates a struct to match struct in C file

    C-code from Matlab:
    /* Type Definitions */
        typedef struct {
        double val;
        emxArray_char_T_1x10 str;
        } struct0_T;
    """

    _fields_ = [
        ("struct_input_val", ct.c_double),
        ("struct_input_str", emxArray_char_T_1x10),
    ]


# initialise struct data
input_string = "INPUT!!"

input_str = emxArray_char_T_1x10()
input_str.data = ct.c_char_p(input_string.encode('utf-8'))

# Pass a list of int
L = [1, len(input_string)]
arr = (ct.c_int * len(L))()
arr[:] = L
input_str.size = arr

sd = testInStruct()
sd.struct_input_val = 2.1
sd.struct_input_str = input_str

libC.test_string_func.argtypes = [ct.POINTER(testInStruct)]
dbl_out = libC.test_string_func(sd)

I have managed to make fairly complex structures work, with varying types and arrays as input. But the cause of the error passing a string in a struct eludes me.

Update: forgot to say that the C code runs when called in Python, but produce an incorrect length of the string inside the C-code and exit code when I try to "fprinf" the passed string inside the C-code.

Any idea what I might do wrong?

By the way, this is the standard format for structures with variable length variables, and those are easily dealt with:

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

Solution

  • The C struct

    struct emxArray_char_T_1x10 {
      char data[10];
      int size[2];
    };
    

    should be matched with Ctypes in Python as

    class emxArray_char_T_1x10(ct.Structure):
        _fields_ = [('data', ct.c_char * 10),
                    ('size', ct.POINTER(ct.c_int))]
    

    Note the difference between char* a and char b[10] in this context. Yes, b can be seen as a pointer to the first element of an array, but the structure contains the actual array, 10 chars, not one pointer to a char.