【问题标题】:How do I use ctypes to recv_into a C buffer multiple times?我如何使用 ctypes 多次将 recv_into 放入 C 缓冲区?
【发布时间】:2017-02-16 20:32:21
【问题描述】:

我有一个 Python 库,它使用 ctypes 向 C 库注册回调。回调的签名是:

CFUNCTYPE(c_int_32, c_uint_32, POINTER(c_byte), POINTER(c_size_t))

在这种情况下,第三个参数是指向 C 库分配的字节数组的指针,第四个参数是它的长度。

我想通过调用socket.recv_into 用来自网络的数据填充字节数组。更重要的是,我想完全填充这个字节数组:也就是说,如果recv_into 返回的字节数少于这个函数的第四个参数,那么我想再次调用recv_into

假设我的函数如下所示:

def callback(first, second, buffer, size):
    total_read = 0
    while total_read < size[0]:
        total_read += some_socket.recv_into(buffer, size[0] - total_read)
        # XXX: What happens here???

我的问题是:如何操纵buffer 的值以确保每次调用recv_into 都附加到缓冲区,而不是覆盖以前写入的数据?

【问题讨论】:

  • 跟进您对另一个答案的评论:如果您投射一片缓冲区,然后执行recv_into,它是否有效?喜欢ctypes.cast(buffer[start:end], ctypes.POINTER(ctypes.c_byte))。缺点是你必须为切片提供一个结尾,否则解释器会抱怨字符串太大。
  • 我不这么认为。缓冲区的一部分给了我一个整数列表,它似乎是从底层缓冲区中复制出来的。那不投。

标签: python ctypes


【解决方案1】:

以 cmets 为基础,无需中间转换:

# assuming we know OFFSET (where to start copying in the buffer)
# and AMOUNT (how many bytes we will read)
tmp_buf = (ctypes.c_char * size).from_address(ctypes.addressof(buffer.contents))
mview = memoryview(tmp_buf)[OFFSET:AMOUNT]
_ = sock.recv_into(mview, AMOUNT)

buffer.contents返回指向缓冲区的指针,因此可以使用ctypes提取其地址

这在一个简单的 C 代码中对我有用,它将预先分配的缓冲区传递到回调中,该回调在 python 代码中注册。

【讨论】:

  • 这非常接近我需要的答案。我最终通过更改回调来减少代码量,以便它接收c_void_p 而不是POINTER(c_byte),这意味着第一行可以变成(ctypes.c_char *size).from_address(buffer)。但是,此代码的其余部分完全正确且运行良好。
【解决方案2】:

我相信诀窍是将其包装到内存视图中:

>>> buf = ctypes.create_string_buffer(b"fooxxx")
>>> sock.recv_into(memoryview(buf)[3:],3)  # receiving b"bar"
3
>>> buf.value
b'foobar'

【讨论】:

  • 恐怕这行不通。要获得像我这样的指针,您必须这样做:buf = ctypes.create_string_buffer(b'fooxxx'); ptr = ctypes.cast(buffer, ctypes.POINTER(c_byte)),然后对ptr 进行操作。如果您尝试这样做,您会发现执行此操作时会收到IndexError
  • 我们解决了@Lukasa 的特殊问题,将POINTER(c_byte) 更改为POINTER(c_void_p),然后使用(c_char * 1).from_address()。对于“正常”的 ctypes 缓冲区,上面的答案是正确的。
【解决方案3】:

这是一个代码示例,展示了如何将recv_into“追加”到缓冲区中。很多关于设置套接字的样板,但这允许您立即运行它并查看它的实际效果。

import socket
import ctypes


_BUFFER_SIZE = 20
BufferType = ctypes.c_byte * _BUFFER_SIZE


if __name__ == '__main__':

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('127.0.0.1', 10000))
    s.listen(1)
    c, _ = s.accept()

    buffer = BufferType()
    offset = 0
    available = _BUFFER_SIZE
    memview = memoryview(buffer)

    while offset < _BUFFER_SIZE:
        print('recv_into: offset=%r, available=%r' % (offset, available))
        count = c.recv_into(memview[offset:], available)
        offset += count
        available -= count

    print('done: buffer=%r' % (memview.tobytes(),))

    c.close()
    s.close()

这能解决您的问题吗?

【讨论】:

  • 不:我不想创建一个新的缓冲区对象并为其分配内存。我想告诉 ctypes 从我已有的指针和大小创建一个新的缓冲区对象,这样它就不会分配新的缓冲区,而是使用 C 代码为我提供的缓冲区。
猜你喜欢
  • 2021-11-21
  • 1970-01-01
  • 1970-01-01
  • 2021-12-11
  • 2012-11-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多