将字符串列表从Python/ctypes传递给期望char **的C函数

24

我有一个C语言函数,它需要一个以\0结尾的字符串列表作为输入:

void external_C( int length , const char ** string_list) { 
   // Inspect the content of string_list - but not modify it.
} 

我想使用Python(带有ctypes)基于Python字符串列表调用此函数:

def call_c( string_list ):
    lib.external_C( ?? )

call_c( ["String1" , "String2" , "The last string"])

有没有关于如何在Python端构建数据结构的建议?请注意,我保证C函数不会更改string_list中字符串的内容。

敬礼

乔基姆


C语言函数如何知道它已经到达了const char *序列的末尾? - habnabit
总的来说,它当然不知道。我的意图是用NULL终止它,或者我可以传递一个长度以及(char **)指针 - 我对所涉及的C库有完全控制权。Joakim - Joakim Hove
如果它不知道,那就没什么用了。你需要以某种方式告诉它,并告诉我们,这样我们才能给你可行的代码。 - habnabit
好的 - 好的;我没有想到那么重要。无论如何,我现在已经更改了C函数的定义,使其将长度参数作为第一个参数。 - Joakim Hove
抱歉,external_C 的 argtypes 应该是什么? - mm_
6个回答

26
def call_c(L):
    arr = (ctypes.c_char_p * len(L))()
    arr[:] = L
    lib.external_C(len(L), arr)

3
注意,在Python 3.x中,默认情况下字符串是unicode编码的,因此你应该通过s.encode('utf-8')bytes(s, 'utf-8')(或其他不是UTF-8的编码)将字符串转换为字节。 - corwin.amber
把这个 ^ 加入到答案中会很好。 - ben26941

7
非常感谢,这很顺利。我还使用了另一种变体,如下所示:
def call_c( L ):
    arr = (ctypes.c_char_p * (len(L) + 1))()
    arr[:-1] = L
    arr[ len(L) ] = None
    lib.external_C( arr )

然后在 C 函数中,我通过迭代(char **)列表,直到找到一个 NULL。


5

使用ctypes

创建到期日期列表(字符串)

expiries = ["1M", "2M", "3M", "6M","9M", "1Y", "2Y", "3Y","4Y", "5Y", "6Y", "7Y","8Y", "9Y", "10Y", "11Y","12Y", "15Y", "20Y", "25Y", "30Y"]

发送字符串数组的逻辑

通过在数组中循环将字符串数组转换为字节数组

 expiries_bytes = []
    for i in range(len(expiries)):
        expiries_bytes.append(bytes(expiries[i], 'utf-8'))

使用逻辑ctypes初始化具有数组长度的指针

expiries_array = (ctypes.c_char_p * (len(expiries_bytes)+1))()

将字节数组分配到指针中
expiries_array[:-1] = expiries_bytes

1
我会尽力为您翻译:

我只是使用 SWIG typemap 制作它

1.在 demo.i 接口文件中编写自定义 typemap。

%module demo

/* tell SWIG to treat char ** as a list of strings */
%typemap(in) char ** {
    // check if is a list
    if(PyList_Check($input))
    {
        int size = PyList_Size($input);
        int i = 0;
        $1 = (char **)malloc((size + 1)*sizeof(char *));
        for(i = 0; i < size; i++)
        {
            PyObject * o = PyList_GetItem($input, i);
            if(PyString_Check(o))
                $1[i] = PyString_AsString(o);
            else
            {
                PyErr_SetString(PyExc_TypeError, "list must contain strings");
                free($1);
                return NULL;
            }
        }
    }
    else
    {
        PyErr_SetString(PyExc_TypeError, "not a list");
        return NULL;
    }
}

// clean up the char ** array
%typemap(freearg) char ** {
    free((char *) $1);
}

2.生成扩展

$ swig -python demo.i  // generate wrap code
$ gcc -c -fpic demo.c demo_wrap.c
$ gcc -shared demo.o demo_wrap.o -o _demo.so

3.在Python中导入模块。

>>> from demo import yourfunction

0

这是一个相当老的问题,但如果人们仍在寻找类似的问题,我认为值得添加。

使用numpy可能是处理所有低级操作和链接库的最简单方法。

example = ["String1" , "String2" , "The last string"]
example_np = m = np.array(example, dtype=np.chararray)

当您构建char*的numpy数组时,您可以使用ctypes获取指向数组的指针,并直接将其提供给期望char**的库。
example_ptr = ctypes.cast(example_np.ctypes.data, ctypes.POINTER(ctypes.c_char_p)) # this is char**

你可以直接调用

lib.external_C(len(example), example_ptr)

0
这是如何将 int 列表 / 字符串列表 / 字符串 / 浮点数传递给 C 代码。
import ctypes

clib = ctypes.cdll.LoadLibrary("main.o")

# Pass a list of int
L = [1, 2, 3, 4, 5]
arr = (ctypes.c_int * len(L))()
arr[:] = L

# Pass a list of string
str_lst = ["hello", "world", "!", "\0"]
str_arr = (ctypes.c_char_p * len(str_lst))()
str_arr[:] = [i.encode("utf-8") for i in str_lst]

x = clib.my_c_fun(1
                  , ctypes.c_float(1.2)
                  , ctypes.c_char_p("hello".encode('utf-8'))
                  , arr
                  , str_arr
                  )

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接