如何使用ctypes多次接收到C缓冲区中的数据?

5
我有一个使用ctypes向C库注册回调函数的Python库。回调函数的签名为:
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))。缺点是您必须提供切片的结束位置,否则解释器会抱怨字符串太大。 - alexpeits
我不这么认为。缓冲区的一部分给了我一个整数列表,看起来是从底层缓冲区复制出来的。那不能转换。 - Lukasa
3个回答

2

参考评论,不需要中间转换的情况下进行构建:

# 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)。然而,这段代码的其余部分完全正确并且运行得很好。 - Lukasa

0

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

>>> 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()来解决@Lukasa的特定问题。对于“正常”的ctypes缓冲区,上面的答案仍然适用。 - Maximilian Hils

0
这是一个代码示例,展示了如何使用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()

这个解决了你的问题吗?


1
不:我不想创建一个新的缓冲区对象并为其分配内存。我想告诉ctypes从我已经拥有的指针和大小创建一个新的缓冲区对象,这样它就不会分配一个新的缓冲区,而是使用C代码提供给我的那个。 - Lukasa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接