我有一个库
my_lib
,其中包含一个 C 函数,该函数采用 char***
参数,即指向由函数分配的 char*
数组的指针。这是此类函数的最小可重现示例:
void getArrayOfStrings(char*** paramPtr)
{
(*paramPtr) = (char**) malloc(3*sizeof(char*));
(*paramPtr)[0] = (char*) malloc(strlen("Foo")+1);
strcpy((*paramPtr)[0], "Foo");
(*paramPtr)[1] = (char*) malloc(strlen("Bar")+1);
strcpy((*paramPtr)[1], "Bar");
(*paramPtr)[2] = 0;
}
它将最后一个数组元素设置为 0,以便调用者可以识别它(而不是提供大小作为第二个参数)。请注意,提供了一个单独的函数来释放内存。
我运行
ctypesgen
来生成与此函数的 Python 绑定。它生成此代码:
getArrayOfStrings = _lib.get("getArrayOfStrings", "cdecl")
getArrayOfStrings.argtypes = [POINTER(POINTER(POINTER(c_char)))]
getArrayOfStrings.restype = None
可以从下面的 Python 脚本调用生成的绑定:
import my_lib
import ctypes
names = ctypes.POINTER(ctypes.POINTER(ctypes.c_char))()
my_lib.getArrayOfStrings(names)
if names:
for name in names:
name_str = my_lib.String(name)
if name_str:
print("Got name: " + str(name_str))
else:
break
它工作得很好并打印“Foo 酒吧 ”
我只是想知道为什么使用
ctypes.POINTER(ctypes.POINTER(ctypes.c_char))
,我理解为“指向字符的指针”,所以char**
可以工作。为什么我不应该使用ctypes.POINTER(ctypes.POINTER(ctypes.POINTER(ctypes.c_char)))
?
我用
ctypes.POINTER(ctypes.POINTER(ctypes.POINTER(ctypes.c_char)))
进行了测试,相同的代码会产生错误:
my_lib.getArrayOfStrings(names)
OSError: exception: access violation writing 0x0000000000000000
因为
ctypes
知道该函数由于定义了 char***
而采用 .argtypes
,所以传递 char**
意味着 ctypes.byref
(获取地址),从而将 names
作为 char***
传递。 这是一个使用显式 byref
并将 C 代码用作 DLL 的工作示例:
import ctypes as ct
dll = ct.CDLL('./test')
getArrayOfStrings = dll.getArrayOfStrings
getArrayOfStrings.argtypes = ct.POINTER(ct.POINTER(ct.POINTER(ct.c_char))),
getArrayOfStrings.restype = None
names = ct.POINTER(ct.POINTER(ct.c_char))()
dll.getArrayOfStrings(ct.byref(names))
#dll.getArrayOfStrings(names) # also works but byref implied here.
i = 0
while names[i]:
print(ct.string_at(names[i]))
i += 1
输出:
b'Foo'
b'Bar'