Python libusb1中的ctypes
参考python libusb1 python 如何调用 c 语言的库?如果优雅的,规范的调用 c 语言的库? 看完 python libusb1 上面的问题,自然就有答案了.
Enum
libusb 里,定义了大量的 c 的枚举.
Enum 用来将 c 里的 cnum 转成 python 里的数据结构,并可以反查.
class Enum(object):
def __init__(self, member_dict, scope_dict=None):
if scope_dict is None:
# Affect caller's locals, not this module's.
# pylint: disable=protected-access
scope_dict = sys._getframe(1).f_locals
# pylint: enable=protected-access
forward_dict = {}
reverse_dict = {}
next_value = 0
for name, value in list(member_dict.items()):
if value is None:
value = next_value
next_value += 1
forward_dict[name] = value
if value in reverse_dict:
raise ValueError('Multiple names for value %r: %r, %r' % (
value, reverse_dict[value], name
))
reverse_dict[value] = name
scope_dict[name] = value
self.forward_dict = forward_dict
self.reverse_dict = reverse_dict
def __call__(self, value):
return self.reverse_dict[value]
def get(self, value, default=None):
return self.reverse_dict.get(value, default)
测试如下:
from libusb1 import Enum as ReverseEnum
e = ReverseEnum({})
e = ReverseEnum({"111":1,"222":2,"333":3})
e.forward_dict['111']
>>1
e(2)
>>'222'
e.get(3)
>>'333
Structure
自然就是用 ctypes 里的 Structure 定义了,同时定义 1 个 pinter 类型:
class libusb_endpoint_descriptor(Structure):
_fields_ = [
('bLength', c_uint8),
('bDescriptorType', c_uint8),
('bEndpointAddress', c_uint8),
('bmAttributes', c_uint8),
('wMaxPacketSize', c_uint16),
('bInterval', c_uint8),
('bRefresh', c_uint8),
('bSynchAddress', c_uint8),
('extra', c_void_p),
('extra_length', c_int)]
libusb_endpoint_descriptor_p = POINTER(libusb_endpoint_descriptor)
也可以这样定义,可以使用前向声明(比如 c 里的链表 Head 结构):
_libusb_transfer_fields = [
('dev_handle', libusb_device_handle_p),
('flags', c_uint8),
('endpoint', c_uchar),
('type', c_uchar),
('timeout', c_uint),
('status', c_int), # enum libusb_transfer_status
('length', c_int),
('actual_length', c_int),
('callback', libusb_transfer_cb_fn_p),
('user_data', c_void_p),
('buffer', c_void_p),
('num_iso_packets', c_int),
('iso_packet_desc', libusb_iso_packet_descriptor)
]
class libusb_transfer(Structure):
pass
libusb_transfer_p = POINTER(libusb_transfer)
libusb_transfer._fields_ = _libusb_transfer_fields
- python 还有能干这个活的内置 struct
比较 struct and ctypes Structure
struct 用来 pack/unpack binarydata,是 ctypes Structure 的底层实现.
ctypes 的 Structure 的优点,像 c 一样使用结构体,比较清晰.
ctypes 的 Structure 的缺点:uses the byte order that is native to C.当需要处理复杂的 bigendian littleendian 时,最好用 struct,而且效率也高些.
restype and argstype
给从 so 里 load 到的函数,定义返回值和参数:
libusb_get_device_list = libusb.libusb_get_device_list
libusb_get_device_list.argtypes = [libusb_context_p, libusb_device_p_p_p]
libusb_get_device_list.restype = c_ssize_t
回调的注册方法
c 里的回调,在 python 里处理,自然是用的 FUNCTYPE
#typedef int (*libusb_hotplug_callback_fn)(libusb_context *ctx,
# libusb_device *device, libusb_hotplug_event event, void *user_data);
libusb_hotplug_callback_fn_p = CFUNCTYPE(
c_int, libusb_context_p, libusb_device_p, c_int, c_void_p)
#int libusb_hotplug_register_callback(libusb_context *ctx,
# libusb_hotplug_event events, libusb_hotplug_flag flags,
# int vendor_id, int product_id, int dev_class,
# libusb_hotplug_callback_fn cb_fn, void *user_data,
# libusb_hotplug_callback_handle *handle);
try:
libusb_hotplug_register_callback = libusb.libusb_hotplug_register_callback
except AttributeError:
pass
else:
libusb_hotplug_register_callback.argtypes = [
libusb_context_p,
c_int, c_int,
c_int, c_int, c_int,
libusb_hotplug_callback_fn_p, c_void_p,
POINTER(libusb_hotplug_callback_handle),
]
libusb_hotplug_register_callback.restype = c_int
稍微特殊的函数
其实也就是用到了 cast.
def libusb_fill_control_setup(
setup_p, bmRequestType, bRequest, wValue, wIndex, wLength):
# 一般情况下,setup_p应该就是libusb_control_setup_p,
# 但是也许有pointer(create_string_buffer())创建的Array pointer,也是可以强制转换的
setup = cast(setup_p, libusb_control_setup_p).contents
setup.bmRequestType = bmRequestType
setup.bRequest = bRequest
setup.wValue = libusb_cpu_to_le16(wValue)
setup.wIndex = libusb_cpu_to_le16(wIndex)
setup.wLength = libusb_cpu_to_le16(wLength)
上面的几个用法搞明白,python 调用 c 不再有什么问题了.