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 int
s. 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).