2017-10-15 5 views
0

J'essaie d'intégrer une fonction Python en C en utilisant PyPy et cffi. Je suis this guide de la documentation de PyPy.Comment puis-je intégrer une fonction Python qui retourne une chaîne en C en utilisant cffi?

Le problème est, tous les exemples que j'ai trouvés fonctionnent sur ints, et ma fonction prend une chaîne et renvoie une chaîne. Je n'arrive pas à comprendre comment intégrer cette fonction dans C, car C ne semble pas avoir de chaînes, plutôt que de faire des tableaux de caractères.

Voici ce que j'ai essayé:

# 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; 
} 

Quand je lance gcc -I/opt/pypy3/include -Wno-write-strings c_tests.c -L/opt/pypy3/bin -lpypy3-c -g -o c_tests puis exécutez ./c_tests, je reçois cette erreur:

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! 

Je don Je n'ai pas beaucoup d'expérience avec C et j'ai l'impression de déformer l'argument chaîne/retour val ue Comment est-ce que je fais cela correctement?

Merci pour votre aide!

Répondre

2

Notez que vous ne devriez pas utiliser l'interface désapprouvée de pypy pour l'intégration; Au lieu de cela, voir http://cffi.readthedocs.io/en/latest/embedding.html.

Le langage C n'a pas de "chaînes", mais uniquement des tableaux de caractères. En C, une fonction qui veut retourner une "chaîne" est généralement écrite différemment: elle accepte comme premier argument un pointeur vers un tampon préexistant (de type char[]), et comme deuxième argument la longueur de ce tampon; et lorsqu'il est appelé, il remplit le tampon. Cela peut être désordonné car vous devez idéalement gérer des situations tampon trop petites dans l'appelant, par ex. allouer un plus grand tableau et appeler à nouveau la fonction.

Alternativement, certaines fonctions abandonnent et retournent un malloc() -ed char *. Ensuite, l'appelant doit se souvenir de free(), sinon une fuite se produit. Je recommanderais cette approche dans ce cas car deviner la longueur maximale de la chaîne avant l'appel pourrait être difficile.

Donc, quelque chose comme ça. En supposant que vous commencez avec http://cffi.readthedocs.io/en/latest/embedding.html, changer plugin.h pour contenir ::

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

Et changer ce bit de 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 *); 
""") 

à partir du code C, vous n'avez pas besoin initialize_api() du tout le nouveau mode d'incorporation ; au contraire, vous dites que #include "plugin.h" et appelez directement la fonction ::

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

Merci de me montrant la bonne documentation - Je vais essayer ceci! – TheInitializer

+0

Êtes-vous sûr de ne pas avoir besoin de 'initialize_api()'? GCC me donne 'une référence indéfinie à 'generate_cool_page''. (J'ai inclus 'plugin.h') – TheInitializer

+0

Lisez très attentivement comment compiler, à la page que j'ai liée à. Il existe deux options pour deux cas d'utilisation différents. Il semble que vous ne suiviez aucune de ces options ... –