使用ctypes.c_char_p 实现的关键是ctypes 对此返回类型进行了特殊处理,它将返回的以null 结尾的字节字符串复制到Python bytes 对象中并返回它。由于实际收到的返回类型是bytes 对象,而不是指针,因此无法访问原始指针。请注意,c_wchar_p 也会发生同样的事情,它被转换为 str 对象。
对于A),解决方案是使用ctypes.POINTER(ctypes.c_char_p),不会发生转换,但如果需要可以手动完成。返回值可以传递回一个释放内存的 C 函数。
对于B),您的想法是正确的。只需将bytes 对象传递给采用const char* 的C 函数,Python 将管理内存。如果 C 函数需要对象在函数调用之后仍然有效(可能用于稍后的回调),请务必保留对该对象的引用,直到不再需要它为止。
对于C),您可以使用任何一种技术。如果 C 将在 C 函数中管理内存分配,请不要使用 c_char_p 获取它,并在 C 函数中释放它;否则,请使用create_string_buffer() 供 Python 管理和保留 C 代码需要的引用。
所有技术的示例:
test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define API __declspec(dllexport)
API char* funca() {
char* p = malloc(6);
strcpy_s(p, 6, "hello");
return p;
}
API void funca_free(void* p) {
*(char*)p = 'x'; // overwrite just to prove accessing a freed buffer
free(p); // that hasn't been re-used yet.
}
API void funcb(const char* p) {
printf("B: %s\n", p);
}
static char* store = NULL;
API void funcc(char* p) {
store = p;
}
API char* funcc_get() {
return store;
}
test.py
import ctypes as ct
dll = ct.CDLL('./test')
dll.funca.argtypes = ()
dll.funca.restype = ct.POINTER(ct.c_char) # NOT c_char_p, char* is lost
dll.funca_free.argtypes = ct.c_void_p,
dll.funca_free.restype = None
dll.funcb.argtypes = ct.c_char_p,
dll.funcb.restype = None
dll.funcc.argtypes = ct.c_char_p,
dll.funcc.restype = None
dll.funcc_get.argtypes = ()
dll.funcc_get.restype = ct.POINTER(ct.c_char) # NOT c_char_p
def funca_wrap():
p = dll.funca() # C-managed
s = ct.cast(p, ct.c_char_p).value # Make a copy as a Python byte string
dll.funca_free(p) # Free by C as needed
return s
print('A:', funca_wrap()) # Use a wrapper to capture string and free buffer
dll.funcb(b'input') # Python-managed and freed
s = ct.create_string_buffer(b'python') # Python-managed
dll.funcc(s)
p = dll.funcc_get()
print('C(py-alloc):', ct.cast(p,ct.c_char_p).value)
del s # Python-freed
print('C(py-freed):', ct.cast(p,ct.c_char_p).value) # access freed memory (could crash)
s = dll.funca() # C-managed (NOT c_char_p)
dll.funcc(s)
p = dll.funcc_get()
print('C(C-alloc):', ct.cast(p,ct.c_char_p).value)
dll.funca_free(s) # C-freed
print('C(C-freed):', ct.cast(p,ct.c_char_p).value) # access freed memory (could crash)
输出:
A: b'hello'
B: input
C(py-alloc): b'python'
C(py-freed): b'\x88\xc9?\xf1\x8a\x01' # note freed memory reused in my case
C(C-alloc): b'hello'
C(C-freed): b'xello' # freed memory wasn't reused yet