Python & Ctypes:将结构作为指针传递给函数以获取数据

Posted

技术标签:

【中文标题】Python & Ctypes:将结构作为指针传递给函数以获取数据【英文标题】:Python & Ctypes: Passing a struct to a function as a pointer to get back data 【发布时间】:2011-05-20 02:23:05 【问题描述】:

我查看了其他答案,但似乎无法让它发挥作用。我试图在 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))

我没有错误。但是,当函数应该返回“0”表示成功并且返回非零表示失败时,该函数会返回 -1。检查 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

【问题讨论】:

嗨马克/亚当,很抱歉延迟回复您的有用答案。我终于拿到了一个逻辑分析仪,可以看到 DLL 的行为与预期不同。在你们给我的帮助下,我现在有了代码。我对 SO 很陌生,我知道不检查正确答案是不礼貌的,但我想将你的两个答案都标记为正确,因为你们都付出了很多努力来帮助我,而且你的代码现在可以工作了。我似乎只能将一个答案标记为正确。你会建议我做什么? 附注非常感谢所有的帮助! 嘿没问题。很高兴你想出来了。不幸的是,您只能投票给一个。这是你的电话。您也可以随时对两者都投赞成票;^) 【参考方案1】:

这是一个工作示例。您似乎将错误的类型传递给函数。

测试 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

【讨论】:

嗨,马克。也感谢你付出了这么多的努力。这是非常赞赏!所以尝试你给我的结果我得到了相同的结果(即从函数返回-1,并且 ARRAY5() 数据元素保持不变。我注意到的一件事是你使用 c_int 作为要传递的 smb_handle 类型作为 SmBusReadByte() 的第一个参数。在头文件中,它被定义为: typedef void *SMBUS_HANDLE; 这就是为什么我将它作为 ctypes.c_void_p 传递。这在这里有很大的不同吗?再次感谢您的帮助! 附注我的想法是 SmBusReadByte 将结构作为指针接收并用返回的数据填充数据数组。为此,我认为我需要 Data 数组的可变内存。这就是我使用 create_string_buffer(SMB_MAX_DATA_SIZE) 的原因。我虽然需要像这样传递可变内存。我误会了吗? 对不起,我的错...我会更正它以供阅读。我模拟了一个写。如果函数返回 -1,您的句柄是否有效? Structures 是可变的。 byref 传递一个指向结构的指针,因此 C 代码可以修改它。不需要 create_string_buffer。 我已经在上面更新了 OpenSmbus。如果您仍然得到 -1,请检查您的其他参数是否正确(地址、命令、块长度)。既然是读取,我怀疑你必须初始化数据。【参考方案2】:

你快到了。您应该使用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

更新

您遇到了问题,因为您需要将open_smbus 的结果类型设置为void*。默认情况下,ctypes 假定函数返回ints。你需要这样说:

open_smbus.restype = ctypes.c_void_p

您收到错误是因为您使用了c_void_p()(请注意额外的括号)。 c_void_pc_void_p() 之间有一个重要的区别。前者是一个type,后者是一个类型的instancec_void_p 代表 C 类型 void*,而 c_void_p() 代表一个实际的指针实例(默认值为 0)。

【讨论】:

嗨亚当,哇,你在那里付出了很多努力。非常感谢。不幸的是,它失败并出现以下错误:ArgumentError:参数 2::预期的 LP_SMB_REQUEST 实例而不是指向 SMB_REQUEST 的指针。知道那里发生了什么吗?谢谢 我基本上使用与 Mark 相同的方法,您已提出建议,但我已更新上述内容以包括我如何初始化他的处理方式。 请看我在上面的问题后发表的评论【参考方案3】:

尝试改变

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

("Data", (c_char * SMB_MAX_DATA_SIZE)]

【讨论】:

嗨,加比,感谢您的回复。我已经在上面尝试过,但出现了错误。

以上是关于Python & Ctypes:将结构作为指针传递给函数以获取数据的主要内容,如果未能解决你的问题,请参考以下文章

使用 ctypes 将 python dict 转换为结构

如何使用ctypes,python将字符串参数传递给C++函数

将 FILE * 从 Python / ctypes 传递给函数

Python - 将字符串对象转换为 ctypes (unsigned char *)

复杂 Ctypes 结构的问题

在 Python/ctypes 中的结构内取消引用 C 函数指针