Search code examples
pythoncfunctionstructurectypes

Complex structure of int32_t and char array in python ctypes


I am trying to create a structure to use in a C library provided (DLL), How the following structure (given in the documentation) can be defined?

#define A 10
#define B 20
typedef struct
{
int32_t size;
int32_t num;
char buf1[A][B];
char buf2[A][B];
char buf3[A][B];
} INSTRUCT;

My attempt to define it in python using ctypes was like so:

from ctypes import*

char_buff1 = ((c_char * 10) * 20)
char_buff2 = ((c_char * 10) * 20)
char_buff3 = ((c_char * 10) * 20)


class INSTRUCT(Structure):
    _fields_=[("size",c_int32),("num",c_int32),("buf1",char_buff1),("buf2",char_buff2),("buf3",char_buff3)]

Can int32_t be replaced with c_int_32 in ctypes? Is it correct way to define the structure?

Then I tried to feed the pointer of the structure to the DLL function and check what it returns as follows:

dlllib = CDLL("some.dll")
somefunction = dlllib.some_function
somefunction.argtypes = [POINTER(INSTRUCT)]

INSTRUCT().size
INSTRUCT().num
print(np.ctypeslib.as_array(INSTRUCT().buf1))

However, I can only the return is 0 and unmodified by the function -- equal to the one defined before the C function call.

I am not sure at which stage the problem occurs, however, there are no errors, the code executes normally. Unfortunately, I don't have the C code available, only the input parameters for the function.

Best regards


Solution

  • The array definition is wrong. In ctypes, the array indices need to be reversed to index the array the way C does. For example, the equivalent of char buf[x][y] in Python with ctypes is buf = (c_char * y * x)(). Note that the bounds are reversed. Otherwise, your definition was correct.

    Note that using c_char will return text characters for array values. If you want integers, use c_int8. I'll use the latter below.

    Example:

    from ctypes import *
    import numpy as np
    
    A,B = 10,20
    ARRAY = c_int8 * B * A   # build as B,A
    
    class INSTRUCT(Structure):
        _fields_=[("size",c_int32),
                  ("num",c_int32),
                  ("buf1",ARRAY),
                  ("buf2",ARRAY),
                  ("buf3",ARRAY)]
    
    i = INSTRUCT()
    i.buf1[9][19] = 1  # access indices as A,B
    print(np.ctypeslib.as_array(i.buf1))
    
    [[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1]]  # 1 in correct location
    

    Your example of accessing used INSTRUCT() which creates a new, zeroed object each time. Create a single instance and pass it to a function like so:

    dlllib = CDLL("some.dll")
    somefunction = dlllib.some_function
    somefunction.argtypes = [POINTER(INSTRUCT)]
    
    i = INSTRUCT()          # make an instance
    somefunction(byref(i))  # byref() passes address of a ctypes object.