pythonpointersstructctypes

Python & Ctypes: Passing a struct to a function as a pointer to get back data


I've looked through other answers but can't seem to get this to work. I'm trying to call a function within a DLL for communicating with SMBus devices. This function takes a pointer to a struct, which has an array as one of it's fields. so...

In C:

typedef struct _SMB_REQUEST
{
    unsigned char Address;
    unsigned char Command;
    unsigned char BlockLength;
    unsigned char Data[SMB_MAX_DATA_SIZE];
} SMB_REQUEST;

I think I have to set values for the Address, Command and BlockLength while the DLL fills the Data array. The function that requires this struct takes it as a pointer

SMBUS_API int SmBusReadByte( SMBUS_HANDLE handle, SMB_REQUEST *request );

So I've set up the struct in Python like so:

class SMB_REQUEST(ctypes.Structure):
    _fields_ = [("Address", c_char),
            ("Command", c_char),
            ("BlockLength", c_char),
            ("Data", type(create_string_buffer(SMB_MAX_DATA_SIZE))]

*Note: I've also tried ctypes.c_char*SMB_MAX_DATA_SIZE for the data type*

To pass a pointer to a struct of this type to the function I have tried to initialise it first as follows:

data = create_string_buffer(SMB_MAX_DATA_SIZE)
smb_request = SMB_REQUEST('\x53', \x00', 1, data)

This responds with:

TypeError: expected string or Unicode object, c_char_Array_32 found

If I try leaving out the data array, like so:

smb_request = SMB_REQUEST('\x53', \x00', 1)

No, error. However, then when I try to pass this to the function:

int_response =  smbus_read_byte(smbus_handle, smb_request))

I get:

ArgumentError: argument 2: <type 'exceptions.TypeError'>: expected LP_SMB_REQUES
T instance instead of SMB_REQUEST

I've tried passing it as a pointer:

int_response =  smbus_read_byte(smbus_handle, ctypes.POINTER(smb_request))

and I get:

----> 1
      2
      3
      4
      5

TypeError: must be a ctypes type

Here's how I've set up the art types:

smbus_read_byte.argtypes = (ctypes.c_void_p, ctypes.POINTER(SMB_REQUEST))

I've tried casting but still no go. Can anyone shed some light on this for me?

Update:

If I first initialise the struct like so:

smb_request = SMB_REQUEST('\xA6', '\x00', chr(1), 'a test string')

and then bass by reference:

int_response =  smbus_receive_byte(smbus_handle, ctypes.byref(smb_request))

I get no error. However, the function returns -1 when it should return '0' for success and non-zero for a fail. Checking the value of smb_request.Data gives back 'a test string' so no change there. Any suggestions as to what might be going on here would be greatly appreciated.

Thanks

UPDATE:

Since I've gotten a couple of enquiries about whether my handle is correct, here's how I'm using it. The header file for the DLL declares the following:

typedef void *SMBUS_HANDLE;

//
// This function call initializes the SMBus, opens the driver and 
// allocates the resources associated with the SMBus.
// All SMBus API calls are valid 
// after making this call except to re-open the SMBus.
//
SMBUS_API SMBUS_HANDLE OpenSmbus(void);

So here's how I'm doing this in python:

smbus_handle = c_void_p() # NOTE: I have also tried it without this line but same result

open_smbus = CDLL('smbus.dll').OpenSmbus
smbus_handle =  open_smbus()
print 'SMBUS_API SMBUS_HANDLE OpenSmbus(void): ' + str(smbus_handle)

I call this before making the call to smbus_read_byte(). I have tried to set open_smbus.restype = c_void_p() but I get an error: TypeError: restype must be a type, a callable, or None


Solution

  • Here's a working example. It looks like you are passing the wrong type to the function and the structure was declared incorrectly for the Data member. Note below how to declare the type of a fixed-size array when declaring the Python structure.

    test.c ("cl /W4 /LD .c" on Windows):

    #include <stdio.h>
    
    #ifdef _WIN32
    #   define SMBUS_API __declspec(dllexport)
    #else
    #   define SMBUS_API
    #endif
    
    #define SMB_MAX_DATA_SIZE 5
    
    typedef void* SMBUS_HANDLE;
    
    typedef struct _SMB_REQUEST {
        unsigned char Address;
        unsigned char Command;
        unsigned char BlockLength;
        unsigned char Data[SMB_MAX_DATA_SIZE];
    } SMB_REQUEST;
    
    SMBUS_API int SmBusReadByte(SMBUS_HANDLE handle, SMB_REQUEST *request) {
        for(unsigned char i = 0; i < request->BlockLength; ++i)
            request->Data[i] = i;
        return request->BlockLength;
    }
    
    SMBUS_API SMBUS_HANDLE OpenSmbus(void) {
        return (void*)0x12345678;
    }
    

    test.py:

    import ctypes as ct
    
    SMB_MAX_DATA_SIZE = 5
    ARRAY = ct.c_ubyte * SMB_MAX_DATA_SIZE
    
    class SMB_REQUEST(ct.Structure):
        _fields_ = (('Address', ct.c_ubyte),
                    ('Command', ct.c_ubyte),
                    ('BlockLength', ct.c_ubyte),
                    ('Data', ARRAY))
    
    dll = ct.CDLL('./test')
    smbus_read_byte = dll.SmBusReadByte
    smbus_read_byte.argtypes = ct.c_void_p, ct.POINTER(SMB_REQUEST)
    smbus_read_byte.restype = ct.c_int
    open_smbus = dll.OpenSmbus
    open_smbus.argtypes = ()
    open_smbus.restype = ct.c_void_p
    
    handle = open_smbus()
    print(f'{handle = :08X}h')
    
    smb_request = SMB_REQUEST(1, 2, 5)
    
    print(f'returned = {smbus_read_byte(handle, smb_request)}')
    print(f'Address = {smb_request.Address}')
    print(f'Command = {smb_request.Command}')
    print(f'BlockLength = {smb_request.BlockLength}')
    for i, b in enumerate(smb_request.Data):
        print(f'Data[{i}] = {b:02X}h')
    

    Output:

    handle = 12345678h
    returned = 5
    Address = 1
    Command = 2
    BlockLength = 5
    Data[0] = 00h
    Data[1] = 01h
    Data[2] = 02h
    Data[3] = 03h
    Data[4] = 04h