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中。