Python & Ctypes: передача структуры в функцию в качестве указателя для возврата данных - PullRequest
16 голосов
/ 04 декабря 2010

Я просмотрел другие ответы, но не могу заставить это работать.Я пытаюсь вызвать функцию в DLL для связи с устройствами SMBus.Эта функция получает указатель на структуру, которая имеет массив в качестве одного из своих полей.Итак ...

В C:

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

Я думаю, что мне нужно установить значения для Address, Command и BlockLength, пока DLL заполняет массив данных.Функция, которая требует эту структуру, принимает ее как указатель

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

Так что я настроил структуру в Python так:

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

* Примечание: я также пыталсяctypes.c_char * SMB_MAX_DATA_SIZE для типа данных *

Чтобы передать указатель на структуру этого типа в функцию, я попытался сначала инициализировать ее следующим образом:

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

Это отвечаетс:

TypeError: expected string or Unicode object, c_char_Array_32 found

Если я попытаюсь пропустить массив данных, например:

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

Нет, ошибка.Однако затем, когда я пытаюсь передать это функции:

int_response =  smbus_read_byte(smbus_handle, smb_request))

, я получаю:

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

Я попытался передать его как указатель:

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

и я получаю:

----> 1
      2
      3
      4
      5

TypeError: must be a ctypes type

Вот как я настроил виды искусства:

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

Я пробовал кастовать, но все еще не пошел.Кто-нибудь может пролить некоторый свет на это для меня?

Обновление:

Если я сначала инициализирую структуру следующим образом:

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

, а затембас по ссылке:

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

Я не получаю ошибки.Однако функция возвращает -1, когда она должна вернуть «0» в случае успеха и ненулевое значение в случае сбоя.Проверка значения smb_request.Data возвращает «тестовую строку», поэтому никаких изменений там нет.Будем очень благодарны за любые предложения относительно того, что здесь может происходить.

Спасибо

ОБНОВЛЕНИЕ:

Так как я получил парувопросы о том, верна ли моя ручка, вот как я ее использую.Заголовочный файл для DLL объявляет следующее:

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);

Итак, вот как я делаю это в 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)

Я вызываю это перед вызовом smbus_read_byte (),Я попытался установить open_smbus.restype = c_void_p(), но я получаю сообщение об ошибке: TypeError: restype must be a type, a callable, or None

Ответы [ 3 ]

26 голосов
/ 04 декабря 2010

Вот рабочий пример. Похоже, вы передаете неправильный тип функции.

Проверка кода DLL ("cl / W4 / LD x.c" в 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

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)

выход

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
7 голосов
/ 04 декабря 2010

Ты почти у цели. Вы должны использовать c_char * SMB_MAX_DATA_SIZE в качестве типа для определения Data. Это работает для меня в Mac OS X:

Общая библиотека:

$ 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 драйвер:

$ 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

У вас проблемы, потому что вам нужно установить тип результата open_smbus на void*. По умолчанию ctypes предполагает, что функции возвращают int s. Вы должны сказать это:

open_smbus.restype = ctypes.c_void_p

Вы получили ошибку, потому что использовали c_void_p() (обратите внимание на дополнительные скобки). Есть важное различие между c_void_p и c_void_p(). Первый - это тип , а второй - экземпляр типа. c_void_p представляет тип C void*, тогда как c_void_p() представляет фактический экземпляр указателя (со значением по умолчанию 0).

1 голос
/ 04 декабря 2010

Попробуйте изменить

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

до

("Data", (c_char * SMB_MAX_DATA_SIZE)]
...