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

前端 未结 3 459
萌比男神i
萌比男神i 2020-11-28 04:16

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

相关标签:
3条回答
  • 2020-11-28 04:56

    Try changing

    ("Data", type(create_string_buffer(SMB_MAX_DATA_SIZE))
    

    to

    ("Data", (c_char * SMB_MAX_DATA_SIZE)]
    
    0 讨论(0)
  • 2020-11-28 05:13

    You're almost there. You should use c_char * SMB_MAX_DATA_SIZE as the type for the definition of Data. This works for me on Mac OS X:

    Shared library:

    $ cat test.c
    #include <stdio.h>
    
    #define SMB_MAX_DATA_SIZE 16
    
    typedef struct _SMB_REQUEST
    {
      unsigned char Address;
      unsigned char Command;
      unsigned char BlockLength;
      unsigned char Data[SMB_MAX_DATA_SIZE];
    } SMB_REQUEST;
    
    int SmBusReadByte(void *handle, SMB_REQUEST *request)
    {
      printf("SmBusReadByte: handle=%p request=[%d %d %d %s]\n", handle, 
          request->Address, request->Command, request->BlockLength, request->Data);
      return 13;
    }
    
    $ gcc test.c -fPIC -shared -o libtest.dylib
    

    Python driver:

    $ cat test.py
    import ctypes
    
    SMB_MAX_DATA_SIZE = 16
    
    class SMB_REQUEST(ctypes.Structure):
        _fields_ = [("Address", ctypes.c_ubyte),
                    ("Command", ctypes.c_ubyte),
                    ("BlockLength", ctypes.c_ubyte),
                    ("Data", ctypes.c_char * SMB_MAX_DATA_SIZE)]
    
    libtest = ctypes.cdll.LoadLibrary('libtest.dylib')
    
    req = SMB_REQUEST(1, 2, 3, 'test')
    
    result = libtest.SmBusReadByte(ctypes.c_voidp(0x12345678), ctypes.byref(req))
    
    print 'result: %d' % result
    
    $ python test.py
    SmBusReadByte: handle=0x12345678 request=[1 2 3 test]
    result: 13
    

    UPDATE

    You're having problems because you need to set the result type of open_smbus to void*. By default, ctypes assumes that functions return ints. You need to say this:

    open_smbus.restype = ctypes.c_void_p
    

    You were getting an error because you were using c_void_p() (note the extra parentheses). There's an important distinction between c_void_p and c_void_p(). The former is a type, and the latter is an instance of a type. c_void_p represents the C type void*, whereas c_void_p() represents an actual pointer instance (with a default value of 0).

    0 讨论(0)
  • 2020-11-28 05:18

    Here's a working example. It looks like you are passing the wrong type to the function.

    Test DLL Code ("cl /W4 /LD x.c" on Windows)

    #include <stdio.h>
    
    #define SMBUS_API __declspec(dllexport)
    #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)
    {
        unsigned char i;
        for(i = 0; i < request->BlockLength; i++)
            request->Data[i] = i;
        return request->BlockLength;
    }
    
    SMBUS_API SMBUS_HANDLE OpenSmbus(void)
    {
        return (void*)0x12345678;
    }
    

    Python code

    from ctypes import *
    SMB_MAX_DATA_SIZE = 5
    ARRAY5 = c_ubyte * SMB_MAX_DATA_SIZE
    
    class SMB_REQUEST(Structure):
        _fields_ = [
            ("Address", c_ubyte),
            ("Command", c_ubyte),
            ("BlockLength", c_ubyte),
            ("Data", ARRAY5)]
    
    smbus_read_byte = CDLL('x').SmBusReadByte
    smbus_read_byte.argtypes = [c_void_p,POINTER(SMB_REQUEST)]
    smbus_read_byte.restype = c_int
    open_smbus = CDLL('x').OpenSmbus
    open_smbus.argtypes = []
    open_smbus.restype = c_void_p
    
    handle = open_smbus()
    print 'handle = %08Xh' % handle
    
    smb_request = SMB_REQUEST(1,2,5)
    
    print 'returned =',smbus_read_byte(handle,byref(smb_request))
    print 'Address =',smb_request.Address
    print 'Command =',smb_request.Command
    print 'BlockLength =',smb_request.BlockLength
    for i,b in enumerate(smb_request.Data):
        print 'Data[%d] = %02Xh' % (i,b)
    

    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
    
    0 讨论(0)
提交回复
热议问题