Python与Ctypes:将结构体作为指针传递给函数以获取数据
我看了其他的回答,但还是没法让这个工作。我想在一个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;
我想我需要设置地址、命令和块长度,同时让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 个回答
试着把
("Data", type(create_string_buffer(SMB_MAX_DATA_SIZE))
改成
("Data", (c_char * SMB_MAX_DATA_SIZE)]
你快到了。你应该把 Data
的定义类型改成 c_char * SMB_MAX_DATA_SIZE
。在 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
更新
你遇到问题是因为需要把 open_smbus
的返回类型设置为 void*
。默认情况下,ctypes 会假设函数返回的是 int
类型。你需要这样写:
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)。
这里有一个可以运行的例子。看起来你传给函数的参数类型不对。
测试 DLL 代码(在 Windows 上使用 "cl /W4 /LD x.c" 编译)
#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