【问题标题】:Is it possible to efficiently pass both bytes and bytearray objects to an external library using ctypes?是否可以使用 ctypes 有效地将字节和字节数组对象传递给外部库?
【发布时间】:2026-01-09 18:50:01
【问题描述】:

假设我在外部库中有以下函数:

void foo(const unsigned char *buf, const int len);

我希望能够使用 ctypes 从我的 Python 代码中调用此函数,而无需复制缓冲区。缓冲区可能非常大,因此避免复制具有明显的性能优势。为了方便我的代码使用者,我希望能够以bytesbytearray 的形式提供此缓冲区。

目前我在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 互换?

【问题讨论】:

  • @eryksun 感谢您的回复。我并不真正关心这是如何实现的。我只想让外部代码在不复制缓冲区内容的情况下接收const unsigned char*。如果可能的话。

标签: python python-2.7 ctypes


【解决方案1】:

您可以创建覆盖from_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 的数组类型。

【讨论】:

  • 再次感谢 Eryk,我真的欠你一杯啤酒!
  • 缓存在 from_param 参数列表中。我对此很感兴趣。 ctypes._CData 中定义的类方法没有 array_t 参数,对吧?它只有一个参数,你在这里命名为 param。但是您添加了一个命名参数array_t,任何调用者都不会提供该参数。您这样做是为了在模块加载时评估默认值,这可能是一个耗时的行为。这样,您只需支付一次该费用。那是对的吗?我以前从未遇到过这种技术!
  • @DavidHeffernan,我使用参数列表稍微优化了查找。我本可以使用一个类属性,它会稍微慢一些。我也可以简单地依赖 ctypes 如何缓存 ctypes.c_char * 0 创建的类型。对于后者,它仍然每次都执行字节码,调用sq_repeat函数,最终调用PyCArrayType_from_ctype,它根据元组键(c_char, 0)返回缓存类型。这可能看起来很多,但与实际创建新类型对象的成本相比,它相对便宜,这就是 ctypes 具有类型缓存的原因。
  • @DavidHeffernan,在参数列表中使用默认参数进行速度破解的技巧通常会被标记为过早优化。这个技巧更常用的地方是 __del__ 方法和其他可以在解释器被拆除时调用的函数和方法——尤其是在 Python 2 中。
最近更新 更多