9

我正在开发一个使用 C 库 libupnp 的 Python 应用程序。我正在使用 CTypes 来使用非常简单的库。我遇到的问题是当我为读取请求注册回调函数时。该函数具有以下形式的原型:

int read_callback(void *pFileHandle, char *pBuf, long nBufLength);

pFileHandle 只是一些文件句柄类型。pBuf 是一个可写的内存缓冲区。这是输出数据的地方。nBufLength 是从文件中读取的字节数。返回状态码。

我有一个 Python 函数指针。这很容易做到,但是当我定义一个 Python 函数来处理这个回调时,我发现 pBuf 没有被写入,因为 Python 字符串是不可变的,并且当你分配或修改它们时,它们会创建新实例。这带来了一个大问题,因为当函数完成请求的文件数据时,C 库期望返回 char 指针。尽管由于 Python 字符串的方式,缓冲区最终每次都是空的。有没有办法在不修改 C 库的情况下解决这个问题?

处理程序应该修改给定的缓冲区参数,这是我的问题。

所以我想要发生的是调用 Python 函数来读取某个文件(可能在内存中、文件系统句柄或介于两者之间的任何文件中)。pBuf 参数由流的读取填充(同样在 Python 中)。然后回调返回到写入 pBuf 的 C 代码。

4

3 回答 3

4

使用 pBuf 和 nBufLength 调用回调。pBuf 已经分配了可写内存,但是如果您要求 pBuf.value,它会转换为不可变的 python 字符串。

而是将 pBuf 转换为可以直接修改的对象:

## If pBuf is c_char_p, then convert to writable object
c = ctypes.cast(pBuf, ctypes.POINTER(ctypes.c_char))
## At this point, you can set individual bytes
## with c[i] = x, but this is dangerous and there's a safer way:

## get address of string
addr = ctypes.addressof(c.contents)

## convert to char[] for safe writing
c2 = (c_char*nBufLength).from_address(addr)

## see how many bytes to write
nb = min(len(msg), nBufLength-1)

c2[:nb] = msg[:nb]
c2[nb+1] = '\0'
于 2013-01-14T02:59:31.097 回答
3

ctypes 可以分配一个缓冲区对象,您的 C 库应该能够写入:

import ctypes
init_size = 256
pBuf = ctypes.create_string_buffer(init_size)

请参阅:http ://docs.python.org/2/library/ctypes.html#ctypes.create_string_buffer

于 2013-01-14T02:57:53.610 回答
0

不要将 pBuf 声明为c_char_p. ctypes将该类型转换为不可变的 Python 字符串,您将无法访问 C 指针地址。您需要将其声明为POINTER(c_char),然后可以使用ctypes.memmove将数据复制到其中。窗口示例:

DLL 代码(在 MSVC 上编译为cl /LD test.c

#ifdef _WIN32
#   define API __declspec(dllexport)
#else
#   define API
#endif

typedef int (*CALLBACK)(void *pFileHandle, char *pBuf, long nBufLength);
char g_buf[10] = "012345678";
CALLBACK g_callback;

API void set_callback(CALLBACK callback) {
    g_callback = callback;
}

API int call_callback() {
    return g_callback(0, g_buf, 10);
}

API const char* get_buf() {
    return g_buf;
}

Python 3 代码:

import ctypes as ct

# Declare the callback type, argument types and return types
CALLBACK = ct.CFUNCTYPE(ct.c_int,ct.c_void_p,ct.POINTER(ct.c_char),ct.c_long)
dll = ct.CDLL('./test')
dll.set_callback.argtypes = CALLBACK,
dll.set_callback.restype = None
dll.call_callback.argtypes = ()
dll.call_callback.restype = ct.c_int
dll.get_buf.argtypes = ()
dll.get_buf.restype = ct.c_char_p

# Decorating a Python function as a callback
#  makes it usable as a ctypes parameter.
@CALLBACK
def callback(handle, buf, length):
    data = b'ABCD\0'
    if length < len(data):
        return 0
    ct.memmove(buf,data,len(data))
    return 1

dll.set_callback(callback)
print(dll.call_callback())
print(dll.get_buf())

输出。请注意,它get_buf返回 ac_char_p并且它是一个字节字符串。const char*价值丢失。

1
b'ABCD'
于 2013-01-18T06:32:00.000 回答