2016-06-21 16 views
9

Ho un file sorgente/header C che fa parte di un progetto più grande. Vorrei testarlo come un'unità, indipendentemente dal vero progetto. Mentre sarebbe possibile farlo in C creando un nuovo progetto con un diverso main(), mi piacerebbe vedere se posso usare Python (3) e le sue strutture (ad esempio il naso) per accelerare la costruzione di test, usare esistenti quadri di riferimento, ecc.Come si può usare CFFI per chiamare una funzione C esistente dato il codice sorgente?

Avevo l'impressione che avrei potuto farlo con CFFI. Ecco un esempio di file C:

// magic.c 
// Implementation of magic. 
int add(int a, int b) 
{ 
    return a; 
} 

L'intestazione:

// magic.h 
// Add two numbers (where a + b is not greater than INT_MAX). 
int add(int a, int b); 

Ecco uno script che cerca solo di compilarlo così posso chiamare alcune funzioni:

# cffi_test.py 
import cffi 

INCLUDE_DIRS = ('.',) 

SOURCES = ('magic.c',) 

ffi = cffi.FFI() 

ffi.set_source(
    '_magic_tests', 
    '#include "magic.h"', 
    include_dirs = INCLUDE_DIRS, 
    sources = SOURCES, 
    libraries = [], 
    ) 

ffi.compile() 

In definitiva ho intenzione di fare in modo che questo sia parte della configurazione prima di una serie di test unitari, ad es. una pura funzione Python test_add() chiamerà e controllerà il risultato della funzione C add() tramite l'oggetto ffi, che è stato creato nell'impostazione di prova.

Lo script sopra sembra funzionare; funziona senza errori, crea un file _magic_tests.c, un file _magic_tests.cp35-win32.pyd e una directory Release. Posso anche import _magic_tests senza errori.

Ma non riesco a capire come chiamare effettivamente una funzione C tramite CFFI. Non riesco a trovare alcuna documentazione per la funzione set_source() e sembra piuttosto integrale per l'intero processo. Il overview lo menziona molto, ma lo reference contiene occorrenze zero di esso. I documenti do hanno una sezione su calling functions, ma si riferisce ad alcuni oggetti lib senza mostrare come è stato creato. Se guardo all'esempio precedente, c'è un oggetto lib creato da ffi.dlopen(), ma non vedo come applicarlo a qualcosa che lo stesso CFFI sta producendo.

La mia domanda (. Cioè la mia X problem) è:

  • CFFI è uno strumento ragionevole da utilizzare per la chiamata e la sperimentazione C funzioni in un multi-piattaforma (Windows 7-10, Linux, OS X) strada e se lo è, come?

Le domande che derivano dal mio approccio attuale sono (vale a dire la mia Y problems.):

  • Dove si trova la documentazione per set_source()? Come posso scoprire quali argomenti ci vuole?
  • Come si producono gli oggetti lib che contengono le funzioni che si desidera chiamare?
  • È questo il modo più semplice per utilizzare CFFI per chiamare una funzione C? Non ho particolarmente bisogno o voglio una libreria condivisa o un pacchetto ridistribuibile da produrre; se deve succedere, va bene, ma non è necessario. Quali altri approcci potrei provare?

La mia messa a punto corrente è:

  • SO: Windows 10
  • Python: CPython 3.5.1 32 bit
  • Pip: 8.1.2
  • CFFI: 1.6.0
  • compilatore C : tutto viene fornito con Visual C++ Build Tools 2015, collegato da this MSDN post

Sto utilizzando CFFI e pycparser da Christoph Gohlke's repository.

risposta

7

Per un mio progetto, utilizzo cffi per testare il codice C. IMHO cffi è un ottimo strumento per generare collegamenti Python per il codice C e quindi pensare che sia uno strumento ragionevole da utilizzare per chiamare e testare le funzioni C da python. Tuttavia, il tuo codice sarà multipiattaforma come il codice C, dal momento che devi compilare il binding per ogni piattaforma.

Di seguito è possibile trovare alcuni riferimenti alla documentazione che dovrebbe rispondere alle vostre domande. Inoltre ho scritto alcuni esempi di codice per illustrare come useresti cffi. Per un esempio più ampio, puoi trovare il mio progetto allo https://github.com/ntruessel/qcgc/tree/master/test.

Quattro tuo esempio, build_magic_tests.py sarebbe simile a questa:

from cffi import FFI 

ffibuilder = FFI() 

# For every function that you want to have a python binding, 
# specify its declaration here 
ffibuilder.cdef(""" 
    int add(int a, int b); 
       """) 

# Here go the sources, most likely only includes and additional functions if necessary 
ffibuilder.set_source("magic_tests", 
    """ 
    #include "magic.h" 
    """, sources=["magic.c"]) 

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

Per generare il modulo magic_tests, è necessario eseguire python build_magic_tests.py. Il modulo generato può essere importato e utilizzato in questo modo:

from magic_tests import ffi, lib 

def run_add(): 
    assert 4 == lib.add(4, 5) 
+0

Ottima risposta! La mia unica ulteriore domanda è: sembra che ci sia qualche duplicazione tra ciò che è nel file di intestazione e ciò che è nella chiamata 'ffibuilder.cdef()'; cioè, dichiarerei la funzione due volte, e c'è il rischio che possa uscire dalla sincronizzazione o introdurre un errore. Pensi che ci sia un modo per ridurre questa duplicazione? – detly

+1

Purtroppo, penso che questo sia al momento impossibile, almeno non ho trovato un modo per farlo. IMHO questo è uno dei principali svantaggi di 'cffi'. Si potrebbe provare a generare automaticamente l'intero 'build_magic_tests.py' usando ancora un altro script. Tuttavia, questo script deve aggirare le limitazioni del metodo 'ffibuilder.cdef()'. – Nicolas

+0

Beh, questo è fastidioso, ma non un rompiscatole. Vale comunque la pena di avere accesso all'ecosistema di test delle unità di Python. (Ed è ancora meno lastra rispetto a qualsiasi framework di test dell'unità C). – detly

Problemi correlati