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

时间:2017-10-15 23:45:31

标签: python c string pypy python-cffi

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

问题是,我发现的所有示例都在int上运行,我的函数接受一个字符串并返回一个字符串。我似乎无法弄清楚如何在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 个答案:

答案 0 :(得分:5)

请注意,您不应该使用pypy不推荐使用的界面进行嵌入;相反,请参阅http://cffi.readthedocs.io/en/latest/embedding.html

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

或者,某些功能会放弃并返回新的malloc() - 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!