如何将字符串列表传递给 CFFI 扩展?

How to pass list of strings to a CFFI extension?

提问人:MaxGyver 提问时间:5/2/2023 最后编辑:MaxGyver 更新时间:5/4/2023 访问量:87

问:

我想将字符串列表传递给需要 a 作为输入参数的 CFFI 扩展。char**

例:

extension.h:

#include <stddef.h>

void sort_strings(char** arr, size_t string_count);

extension.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int cmpstrp(const void *p1, const void *p2) {
    return strlen(*(const char**)p1) < strlen(*(const char**)p2);
}

void sort_strings(char** arr, size_t string_count) {
    qsort(arr, string_count, sizeof(char*), cmpstrp);
}

// `main` is defined only for testing if `sort_strings` works as expected
int main(void) {
    char* string_list[] = {"cat", "penguin", "mouse"};
    size_t string_count = sizeof(string_list)/sizeof(string_list[0]);
    sort_strings(string_list, string_count);
    for (int i = 0; i < string_count; i++) {
        printf("%s\n", string_list[i]);
    }
}

extension_build.py:

from cffi import FFI

ffibuilder = FFI()

ffibuilder.cdef('void sort_strings(char** arr, size_t string_count);')

ffibuilder.set_source('_extension',
    '#include "extension.h"',
    sources=['extension.c'],
    libraries=[])

if __name__ == "__main__":
    ffibuilder.compile(verbose=True)

demo.py:

from _extension.lib import sort_strings
from _extension import ffi


string_list = ["cat", "penguin", "mouse"]
bytes_list = [s.encode("latin1") for s in string_list]
cdata_list = ffi.new("char **", bytes_list)

sort_strings(cdata_list, len(string_list))

# print sorted list

如何测试:

python extension_build.py
python demo.py

我尝试将 ,并作为第一个输入参数传递给 。我收到以下错误消息:string_listbytes_listcdata_listsort_strings

TypeError: initializer for ctype 'char *' must be a cdata pointer, not str
TypeError: initializer for ctype 'char *' must be a cdata pointer, not bytes
TypeError: initializer for ctype 'char *' must be a cdata pointer, not list

如何正确传递我的字符串列表(如果可能,不复制列表)?

(以防万一我的意图不明确:我不是在问如何在 Python 中对列表进行排序。

溶液:

(基于Armin Rigo的回答。

它像这样更新时起作用:demo.py

from _extension.lib import sort_strings
from _extension import ffi


string_list = ["cat", "penguin", "mouse"]
bytes_list = [ffi.new("char[]", s.encode("latin1")) for s in string_list]
pointer = ffi.new("char*[]", bytes_list)

sort_strings(pointer, len(string_list))

for s in pointer:
    print(ffi.string(s).decode("latin1"))
蟒蛇 C python-cffi

评论


答:

1赞 Armin Rigo 5/3/2023 #1

您需要将

cdata_list = ffi.new("char **", bytes_list)

cdata_list = ffi.new("char *[]", bytes_list)

因为分配一个 single 并返回指向它的指针,而分配一个 multi 的数组。ffi.new("T*")Tffi.new("T[]", length_or_list)T

编辑:

啊不,另一个问题是你必须显式分配每个,所以你需要:char *

bytes_list = [ffi.new("char[]", s.encode("latin1")) for s in string_list]

然后这可以直接作为参数传递给(或者你可以像上面一样传递,但如果你只是直接传递,CFFI 也会做同样的事情)。bytes_listsort_string()cdata_listbytes_list

编辑2:

传递 or 不是 100% 等价的:如果你做了一个显式并在调用中使用它,那么你可以在调用后从中读取它,以了解 C 函数如何更改这个 C 数组(就像在你的示例中所做的那样)。如果改为传递,则 CFFI 会在调用之前将其转换为相同的 C 数组,但在调用后立即释放 C 数组。bytes_listcdata_listcdata_listbytes_list

评论

0赞 MaxGyver 5/3/2023
谢谢。这将运行而不会出错。但它没有按预期工作:调用 后列表没有排序。好像收到的是列表的副本而不是指针。知道为什么吗?sort_stringssort_strings
0赞 MaxGyver 5/3/2023
我可以验证这一点:现在我正在打印其中的列表:这里是排序的。sort_strings
0赞 MaxGyver 5/3/2023
好的,它的工作原理是这样的:创建一个指向 : 的指针。然后将此指针传递给函数:.bytes_listpointer = ffi.new("char*[]", bytes_list)sort_strings(pointer, len(string_list))
1赞 Armin Rigo 5/4/2023
是的,如果你传递了一个普通列表,那么它不会被调用修改(它在调用之前在内部转换为 C 数组,并且 C 数组在调用后立即释放)。但是,如果您传递一个 cdata 类型,那么它作为 C 数组将反映在 C 中所做的更改。char *[]