在Python中使用ctypes创建指针数组



我想使用ctypes创建一个python数据类型,它与c数据类型&;const char**&;相匹配,它类似于指针数组。但是,我无法在Python中编写此代码。简化的c函数头看起来像这样:

int foo(int numOfProp, const char** propName, const char** propValue);
在C中,正确的函数调用应该是这样的:
const char *PropName[2];
PropName[0] = "Prop_Index_1";
PropName[1] = "Prop_Index_2";
const char *PropValue[2];
PropValue[0] = "10";
PropValue[1] = "20";
stream_id = (*foo)(2, PropName, PropValue);
基本上,该函数接受两个数组(一对name和value)以及两个数组的长度,并返回一个流ID。当加载DLL时,我可以看到函数期望属性数组使用ctypes数据类型:

"LP_c_char_p">

然而,我真的很难基于字符串列表创建这种数据类型。

我的第一次尝试(基于我如何创建一个指向指针数组的Python ctypes指针)看起来像这样:

# set some dummy values
dummy_prop_values = [
"10",
"20"
]
# create property dict
properties = {
f"Prop_Index_{i}": dummy_prop_values[i] for i in range(len(dummy_prop_values))
}
def first_try():
# create a dummy ctype string
ctypes_array = ctypes.c_char_p * 2
# create empty c-type arrays for the stream properties
prop_names = ctypes_array()
prop_values = ctypes_array()
# fill the empty arrays with their corresponding values
for i, (prop_name, prop_value) in enumerate(properties.items()):
prop_names[i] = prop_name.encode()
prop_values[i] = prop_value.encode()
# get pointer to properties
ptr_prop_names = ctypes.pointer(prop_names)
ptr_prop_values = ctypes.pointer(prop_values)
return ptr_prop_names, ptr_prop_values

当我把返回值交给函数foo(这实际上是有意义的,因为我显式地创建了一个长度为2的数组)时,它会抛出这种错误…我不知道这对另一个问问题的人是如何/为什么起作用的):

ctypes.ArgumentError: argument 2: <class 'TypeError'>: expected LP_c_char_p instance instead of LP_c_char_p_Array_2

我的第二次尝试(或多或少基于我自己的想法)是这样的:

def second_try():
# convert properties to lists
prop_names = [x for x in properties.keys()]
prop_values = [x for x in properties.values()]

# concat list elements, zero terminated
# but I guess this is wrong anyway because it leads to an early string-termination (on byte-level)...?
prop_names = ctypes.c_char_p("".join(prop_names).encode())
prop_values = ctypes.c_char_p("".join(prop_values).encode())

# get pointer to properties
ptr_prop_names = ctypes.pointer(prop_names)
ptr_prop_values = ctypes.pointer(prop_values)
return ptr_prop_names, ptr_prop_values

这实际上不会抛出错误,而是返回-1作为流ID,这表示"创建流不成功"。我仔细检查了函数调用的所有其他参数,这两个属性是唯一可能出错的。

不知什么原因,我就是不知道我到底在哪里出错了,但希望这里有人能给我指出正确的方向。

要将某种类型的列表转换为该类型的ctypes数组,简单的习惯用法是:

(element_type * num_elements)(*list_of_elements)

在本例中:

(c_char_p * len(array))(*array)

注意,(*array)扩展数组,就好像每个单独的元素都是作为参数传递的,这是初始化数组所必需的。

完整的示例:

test.c—验证参数是否按预期传递。

#include <stdio.h>
#ifdef _WIN32
#   define API __declspec(dllexport)
#else
#   define API
#endif
API int foo(int numOfProp, const char** propName, const char** propValue) {
for(int i = 0; i < numOfProp; i++)
printf("name = %s    value = %sn", propName[i], propValue[i]);
return 1;
}

test.py

import ctypes as ct
dll = ct.CDLL('./test')
# Always define .argtypes and .restype to help ctypes error checking
dll.foo.argtypes = ct.c_int, ct.POINTER(ct.c_char_p), ct.POINTER(ct.c_char_p)
dll.foo.restype = ct.c_int
# helper function to build ctypes arrays
def make_charpp(arr):
return (ct.c_char_p * len(arr))(*(s.encode() for s in arr))
def foo(arr1, arr2):
if len(arr1) != len(arr2):
raise ValueError('arrays must be same length')
return dll.foo(len(arr1) ,make_charpp(arr1), make_charpp(arr2))
foo(['PropName1', 'PropName2'], ['10', '20'])

输出:

name = PropName1    value = 10
name = PropName2    value = 20

最新更新