Python 是否可以使用ctypes将字节和bytearray对象有效地传递到外部库?
假设我在外部库中具有以下函数:Python 是否可以使用ctypes将字节和bytearray对象有效地传递到外部库?,python,python-2.7,ctypes,Python,Python 2.7,Ctypes,假设我在外部库中具有以下函数: void foo(const unsigned char *buf, const int len); 我希望能够使用ctypes从Python代码中调用此函数,而无需复制缓冲区。缓冲区可能相当大,因此避免复制具有明显的性能优势。为了方便代码使用者,我希望能够以bytes或bytearray的形式提供此缓冲区 目前,我在我的argtypes声明中将buf声明为ctypes.POINTER(ctypes.c_char) lib.foo.argtypes = [cty
void foo(const unsigned char *buf, const int len);
我希望能够使用ctypes
从Python代码中调用此函数,而无需复制缓冲区。缓冲区可能相当大,因此避免复制具有明显的性能优势。为了方便代码使用者,我希望能够以bytes
或bytearray
的形式提供此缓冲区
目前,我在我的argtypes
声明中将buf
声明为ctypes.POINTER(ctypes.c_char)
lib.foo.argtypes = [ctypes.POINTER(ctypes.c_char), ctypes.c_int]
buf = bytes(...)
lib.foo(buf, len(buf))
这很好,我可以传递一个bytes
对象。但是,如果我传递一个bytearray
对象,则会遇到以下错误:
ctypes.ArgumentError:参数1::错误类型
是否有一种方法允许传递
bytearray
,最好与bytes
互换?您可以创建指针类型的子类,该子类重写\u param中的,以适应bytearray
。例如:
class Pchar(ctypes.POINTER(ctypes.c_char)):
_type_ = ctypes.c_char
@classmethod
def from_param(cls, param, array_t=ctypes.c_char * 0):
if isinstance(param, bytearray):
param = array_t.from_buffer(param)
return super(Pchar, cls).from_param(param)
lib.foo.argtypes = [Pchar, ctypes.c_int]
为bytearray
创建的c_char
数组只需要通过Python的缓冲协议获取对象的内部缓冲区。数组大小无关紧要,因此我们可以避免为bytearray
的每个可能长度创建数组子类。只需使用缓存在from_param
参数列表中的长度为0的数组类型。您可以创建指针类型的子类,该子类重写from_param
以适应字节数组。例如:
class Pchar(ctypes.POINTER(ctypes.c_char)):
_type_ = ctypes.c_char
@classmethod
def from_param(cls, param, array_t=ctypes.c_char * 0):
if isinstance(param, bytearray):
param = array_t.from_buffer(param)
return super(Pchar, cls).from_param(param)
lib.foo.argtypes = [Pchar, ctypes.c_int]
为bytearray
创建的c_char
数组只需要通过Python的缓冲协议获取对象的内部缓冲区。数组大小无关紧要,因此我们可以避免为bytearray
的每个可能长度创建数组子类。只需使用缓存在from_param
参数列表中的长度为0的数组类型。@eryksun感谢您的响应。我并不真正关心这是如何实现的。我只希望外部代码接收一个const unsigned char*
,而不复制缓冲区的内容。如果可能的话。@eryksun感谢您的回复。我并不真正关心这是如何实现的。我只希望外部代码接收一个const unsigned char*
,而不复制缓冲区的内容。如果可能的话,再次谢谢你,艾瑞克,我真的欠你一杯啤酒!缓存在from_param参数列表中。我对此很感兴趣。ctypes.\u CData
中定义的classmethod没有数组\u t
参数,对吗?它只有一个参数,您在这里将其命名为param。但是您添加了一个命名参数,array\u t
,任何调用方都不会提供该参数。这样做是为了在模块加载时计算默认值,这可能是一个耗时的操作。这样你只需付一次费用。对吗?我以前从未见过这种技术@DavidHeffernan,我使用参数列表稍微优化了查找。我可以使用class属性,这稍微慢一点。我还可以简单地依赖ctypes如何缓存由ctypes.c_char*0
创建的类型。对于后者,它仍然需要每次执行字节码,调用sq\u repeat
函数,并最终从\u ctype
调用PyCArrayType\u,该函数基于元组键(c\u char,0)
返回缓存类型。这看起来可能很多,但与实际创建一个新类型对象的成本相比,它相对便宜,这就是为什么ctypes有类型缓存的原因。@DavidHeffernan,在参数列表中使用默认参数进行速度攻击的技巧通常会被标记为过早优化。这个技巧更常用的地方是\uuu del\uuu
方法以及在解释器被拆除时可以调用的其他函数和方法——特别是在Python 2中。再次感谢Eryk,我真的欠你一杯啤酒!缓存在from_param参数列表中。我对此很感兴趣。ctypes.\u CData
中定义的classmethod没有数组\u t
参数,对吗?它只有一个参数,您在这里将其命名为param。但是您添加了一个命名参数,array\u t
,任何调用方都不会提供该参数。这样做是为了在模块加载时计算默认值,这可能是一个耗时的操作。这样你只需付一次费用。对吗?我以前从未见过这种技术@DavidHeffernan,我使用参数列表稍微优化了查找。我可以使用class属性,这稍微慢一点。我还可以简单地依赖ctypes如何缓存由ctypes.c_char*0
创建的类型。对于后者,它仍然需要每次执行字节码,调用sq\u repeat
函数,并最终从\u ctype
调用PyCArrayType\u,该函数基于元组键(c\u char,0)
返回缓存类型。这看起来可能很多,但与实际创建一个新类型对象的成本相比,它相对便宜,这就是为什么ctypes有类型缓存的原因。@DavidHeffernan,在参数列表中使用默认参数进行速度攻击的技巧通常会被标记为过早优化。在\uu del\uu
方法中更常用这种技巧,以及在解释器被拆除时可以调用的其他函数和方法,尤其是在Python 2中。