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

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


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 5typedef 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 = 5ARRAY5 = c_ubyte * SMB_MAX_DATA_SIZEclass SMB_REQUEST(Structure):    _fields_ = [        ("Address", c_ubyte),        ("Command", c_ubyte),        ("BlockLength", c_ubyte),        ("Data", ARRAY5)]smbus_read_byte = CDLL('x').SmBusReadBytesmbus_read_byte.argtypes = [c_void_p,POINTER(SMB_REQUEST)]smbus_read_byte.restype = c_intopen_smbus = CDLL('x').OpenSmbusopen_smbus.argtypes = []open_smbus.restype = c_void_phandle = open_smbus()print 'handle = %08Xh' % handlesmb_request = SMB_REQUEST(1,2,5)print 'returned =',smbus_read_byte(handle,byref(smb_request))print 'Address =',smb_request.Addressprint 'Command =',smb_request.Commandprint 'BlockLength =',smb_request.BlockLengthfor i,b in enumerate(smb_request.Data):    print 'Data[%d] = %02Xh' % (i,b)

Output

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


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 16typedef 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.pyimport ctypesSMB_MAX_DATA_SIZE = 16class 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.pySmBusReadByte: 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).


Try changing

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

to

("Data", (c_char * SMB_MAX_DATA_SIZE)]