如何使用 cffi 在 C 中嵌入一个返回字符串的 Python 函数?

Posted

技术标签:

【中文标题】如何使用 cffi 在 C 中嵌入一个返回字符串的 Python 函数?【英文标题】:How can I embed a Python function that returns a string in C using cffi? 【发布时间】:2018-03-27 10:25:19 【问题描述】:

我正在尝试使用 PyPy 和 cffi 在 C 中嵌入 Python 函数。我正在关注 PyPy 文档中的 this guide。

问题是,我发现的所有示例都在整数上运行,而我的函数接受一个字符串并返回一个字符串。我似乎无法弄清楚如何在 C 中嵌入这个函数,因为 C 似乎并没有真正的字符串,而是使用字符数组。

这是我尝试过的:

# interface.py

import cffi

ffi = cffi.FFI()
ffi.cdef('''
struct API 
    char (*generate_cool_page)(char url[]);
;
''')

...


@ffi.callback("char[] (char[])")
def generate_cool_page(url):
    # do some processing with BS4
    return str(soup)

def fill_api(ptr):
    global api 
    api = ffi.cast("struct API*", ptr)
    api.generate_cool_page = generate_cool_page

--

// c_tests.c

#include "PyPy.h"
#include <stdio.h>
#include <stdlib.h>

struct API 
    char (*generate_cool_page)(char url[]);
;

struct API api;   /* global var */

int initialize_api(void)

    static char source[] =
        "import sys; sys.path.insert(0, '.'); "
        "import interface; interface.fill_api(c_argument)";
    int res;

    rpython_startup_code();
    res = pypy_setup_home(NULL, 1);
    if (res) 
        fprintf(stderr, "Error setting pypy home!\n");
        return -1;
    
    res = pypy_execute_source_ptr(source, &api);
    if (res) 
        fprintf(stderr, "Error calling pypy_execute_source_ptr!\n");
        return -1;
    
    return 0;


int main(void)

    if (initialize_api() < 0)
        return 1;

    printf(api.generate_cool_page("https://example.com"));

    return 0;

当我运行 gcc -I/opt/pypy3/include -Wno-write-strings c_tests.c -L/opt/pypy3/bin -lpypy3-c -g -o c_tests 然后运行 ​​./c_tests 时,我收到此错误:

debug: OperationError:
debug:  operror-type: CDefError
debug:  operror-value: cannot render the type <char()(char *)>: it is a function type, not a pointer-to-function type
Error calling pypy_execute_source_ptr!

我没有大量的 C 语言经验,我觉得我在歪曲字符串参数/返回值。我该如何正确地做到这一点?

感谢您的帮助!

【问题讨论】:

【参考方案1】:

请注意,您不应该使用 pypy 已弃用的接口来嵌入;相反,请参阅http://cffi.readthedocs.io/en/latest/embedding.html

C 语言没有“字符串”,只有字符数组。在 C 中,通常会编写一个想要返回“字符串”的函数 不同的是:它接受一个指向预先存在的缓冲区(类型为char[])的指针作为第一个参数,作为第二个参数接受该缓冲区的长度;当被调用时,它会填充缓冲区。这可能会很混乱,因为理想情况下您需要处理调用者中缓冲区太小的情况,例如分配一个更大的数组并再次调用该函数。

另外,一些函数放弃并返回一个新的malloc()-ed char *。那么调用者一定要记得free()它,否则就会发生泄漏。在这种情况下,我会推荐这种方法,因为在调用之前猜测字符串的最大长度可能很困难。

所以,类似的事情。假设你从 http://cffi.readthedocs.io/en/latest/embedding.html,更改 plugin.h 包含::

// return type is "char *"
extern char *generate_cool_page(char url[]);

并更改plugin_build.py::

ffibuilder.embedding_init_code("""
    from my_plugin import ffi, lib

    @ffi.def_extern()
    def generate_cool_page(url):
        url = ffi.string(url)
        # do some processing
        return lib.strdup(str(soup))   # calls malloc()
""")
ffibuilder.cdef("""
    #include <string.h>
    char *strdup(const char *);
""")

从 C 代码中,您根本不需要 initialize_api() 新的嵌入模式;相反,您只需说#include "plugin.h" 并直接调用函数::

char *data = generate_cool_page("https://example.com");
if (data == NULL)  handle_errors... 
printf("Got this: '%s'\n", data);
free(data);   // important!

【讨论】:

感谢您为我指出正确的文档 - 我会尝试一下! 你确定我不需要initialize_api()吗? GCC 给了我undefined reference to 'generate_cool_page'。 (我已经收录了plugin.h 在我链接到的页面上仔细阅读如何编译。两种不同的用例有两种选择。您似乎没有遵循这些选项... 糟糕,抱歉。我有点累,不小心忽略了部分文档。在从 cdef 调用中删除 #include &lt;string.h&gt;(它不会用它编译,groups.google.com/forum/#!topic/python-cffi/vDAw37NHRSg)、构建插件并弄清楚如何使用 gcc 之后,我有这个错误:TypeError: initializer for ctype 'char *' must be a bytes or list or tuple, not str。提到的行是return lib.strdup(str(soup)) 语句。 啊,我真的需要学习真正阅读错误信息。将str(soup) 转换为bytes 对象后,它就可以工作了。感谢您的帮助!

以上是关于如何使用 cffi 在 C 中嵌入一个返回字符串的 Python 函数?的主要内容,如果未能解决你的问题,请参考以下文章

在 Python CFFI 中声明包含 time_t 字段的结构

使用带有嵌入式 PyPy 的外部“Python”样式 cffi 回调

如何使用 CFFI 将包含其标头的 C 库包装到 python 程序中?

如何使用 Python CFFI 正确包装 C 库

使用 CFFI 从 Python 传递指向 C 函数的指针的最佳方法

如何使用 cffi-lua 向/从 C 函数传递 Lua 表