我正在开发一个使用libupnp的Python应用程序,它是一个C库。我正在使用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代码。
答案 0 :(得分:4)
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
答案 1 :(得分:2)
使用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'
答案 2 :(得分:1)
不要将pBuf声明为c_char_p
。 ctypes
将该类型转换为不可变的Python字符串。您需要将其声明为POINTER(c_char)
。一个快速而肮脏的Windows示例:
DLL代码(在MSVC上编译为cl /LD test.c
)
typedef int (*CALLBACK)(void *pFileHandle, char *pBuf, long nBufLength);
char g_buf[10] = "012345678";
CALLBACK g_callback;
__declspec(dllexport) void set_callback(CALLBACK callback) { g_callback = callback; }
__declspec(dllexport) void call_callback() { g_callback(0,g_buf,10); }
__declspec(dllexport) const char* get_buf() { return g_buf; }
Python 3.X代码:
from ctypes import *
# Declare the callback type, argument types and return types
CALLBACK = CFUNCTYPE(c_int,c_void_p,POINTER(c_char),c_long)
dll = CDLL('x')
dll.set_callback.argtypes = [CALLBACK]
dll.set_callback.restype = None
dll.call_callback.argtypes = []
dll.call_callback.restype = None
dll.get_buf.argtypes = []
dll.get_buf.restype = c_char_p
@CALLBACK
def callback(handle,buf,length):
for i in range(9):
buf[i] = ord('A') + i
buf[9] = 0
return 0
dll.set_callback(callback)
dll.call_callback()
print(dll.get_buf())
输出:
b'ABCDEFGHI'