2015-10-23 15 views
9

Ho una funzione di libreria (scritta in C) che genera testo scrivendo l'output su FILE *. Voglio racchiuderlo in Python (2.7.x) con codice che crea un file temporaneo o pipe, lo passa nella funzione, legge il risultato dal file e lo restituisce come una stringa Python.Passare FILE * in funzione da Python/ctypes

Ecco un esempio semplificato per illustrare quello che sto cercando:

/* Library function */ 
void write_numbers(FILE * f, int arg1, int arg2) 
{ 
    fprintf(f, "%d %d\n", arg1, arg2); 
} 

Python avvolgitore:

from ctypes import * 
mylib = CDLL('mylib.so') 


def write_numbers(a, b): 
    rd, wr = os.pipe() 

    write_fp = MAGIC_HERE(wr) 
    mylib.write_numbers(write_fp, a, b) 
    os.close(wr) 

    read_file = os.fdopen(rd) 
    res = read_file.read() 
    read_file.close() 

    return res 

#Should result in '1 2\n' being printed. 
print write_numbers(1,2) 

mi chiedo qual è il mio migliore scommessa è per MAGIC_HERE().

Sono tentato di utilizzare solo ctypes e creare un wrapper libc.fdopen() che restituisce un Python c_void_t, quindi lo passa nella funzione di libreria. Credo che in teoria dovrebbe essere sicuro - basta chiedersi se ci sono problemi con questo approccio o un Python-ismo esistente per risolvere questo problema.

Inoltre, questo andrà in un processo di lunga durata (assumiamo solo "per sempre"), quindi eventuali descrittori di file trapelati saranno problematici.

+0

'os .popen() 'non è corretto. Richiede almeno un argomento, la riga di comando per invocare e ottenere pipe. Inoltre, è deprecato in favore di "sottoprocesso", come [i documenti] (https://docs.python.org/2/library/os.html?highlight=os.popen#os.popen) dicono. –

+0

Mi spiace, intendevo 'os.pipe()'. Aggiornato. –

+1

A meno che non si stia pianificando di eseguirlo su Windows, che ha il problema di librerie di runtime C potenzialmente non corrispondenti, allora non penso che avremo problemi nel chiamare 'libc.fdopen' e passare il puntatore' FILE' risultante .Ma invece di usare 'c_void_p', creerei un opaco' classe FILE (Structure): pass' e metto 'libc.fdopen.restype = POINTER (FILE)'. Questo non verrà convertito in un risultato intero. OTOH, 'c_void_p' come' restype' viene convertito in un intero, quindi dovresti assicurarti che 'mylib.write_numbers.argtypes' sia impostato anche per evitare di troncare un valore puntatore a 64 bit. – eryksun

risposta

3

Per prima cosa, notare che FILE* è un'entità specifica per stdio. Non esiste a livello di sistema. Le cose che esistono a livello di sistema sono descrittori (recuperati con file.fileno()) in UNIX (os.pipe() restituisce già descrittori semplici) e gestisce (recuperati con msvcrt.get_osfhandle()) in Windows. Quindi è una scelta sbagliata come formato di scambio tra librerie se può esistere più di un runtime C in azione. Ti troverai nei guai se la tua libreria è compilata su un altro runtime C della tua copia di Python: 1) i layout binari della struttura potrebbero essere diversi (ad esempio a causa dell'allineamento o di membri aggiuntivi a scopo di debug o di dimensioni di testo diverse); 2) in Windows, i descrittori di file a cui la struttura si collega sono entità specifiche di C, e la loro tabella è gestita internamente da un runtime C 1.

Inoltre, in Python 3, l'I/O è stato revisionato per districarlo da stdio. Quindi, FILE* è estraneo a quel sapore Python (e probabilmente anche alla maggior parte dei sapori non C).

Ora, ciò che è necessario è quello di

  • qualche modo indovinare quale runtime C avete bisogno, e
  • chiamare il suo fdopen() (o equivalente).

(Uno dei motti di Python è "fare la cosa giusta facile e la cosa sbagliata duro", dopo tutto)


Il metodo più pulito è quello di utilizzare l'istanza precisa che la biblioteca è collegato (prego che sia collegato dinamicamente o non ci sarà alcun simbolo esportato da chiamare)

Per il primo elemento, non sono riuscito a trovare alcun modulo Python in grado di analizzare i metadati dei moduli dinamici caricati per scoprirlo quali DLL/così è stato collegato con (solo un nome o anche name + version non è abbastanza, lo sai, a causa di possibili istanze multiple della libreria sul sistema).Anche se è sicuramente possibile dal momento che le informazioni sul suo formato sono ampiamente disponibili.

Per il secondo articolo, è un banale ctypes.cdll('path').fdopen (_fdopen per MSVCRT).


In secondo luogo, si può fare un piccolo modulo di supporto che sarebbe stato compilato con lo stesso tempo di esecuzione (o garantiti compatibile) come la biblioteca e farebbe la conversione dal descrittore di cui sopra/gestire per voi. Questa è una soluzione efficace per modificare la libreria corretta.


Infine, c'è il più semplice (e più sporca) metodo che utilizza esempio C runtime di Python (quindi tutti gli avvertimenti suddetti si applicano integralmente) attraverso pitone C API disponibile tramite ctypes.pythonapi. Sfrutta

  • il fatto che gli oggetti di file-come Python 2 di sono involucri oltre stdio s' FILE* (3 Python di non lo sono)
  • PyFile_AsFile API che restituisce il avvolto FILE* (si noti che it's missing from Python 3)
    • per un autonomo fd, è necessario costruire un oggetto simile a file prima (in modo che ci sarebbe stato un FILE* di tornare;))
  • il fatto che id() di un oggetto è il suo indirizzo di memoria (CPython-specifico) 2

    >>> open("test.txt") 
    <open file 'test.txt', mode 'r' at 0x017F8F40> 
    >>> f=_ 
    >>> f.fileno() 
    3 
    >>> ctypes.pythonapi 
    <PyDLL 'python dll', handle 1e000000 at 12808b0> 
    >>> api=_ 
    >>> api.PyFile_AsFile 
    <_FuncPtr object at 0x018557B0> 
    >>> api.PyFile_AsFile.restype=ctypes.c_void_p #as per ctypes docs, 
                 # pythonapi assumes all fns 
                 # to return int by default 
    >>> api.PyFile_AsFile.argtypes=(ctypes.c_void_p,) # as of 2.7.10, long integers are 
           #silently truncated to ints, see http://bugs.python.org/issue24747 
    >>> api.PyFile_AsFile(id(f)) 
    2019259400 
    

Non tenere a mente che con fd s e puntatori C, è necessario garantire una corretta oggetto vita a mano!

  • oggetti simile a file restituiti da os.fdopen() fanno chiudere il descrittore su .close()
    • descrittori così duplicati con os.dup() se ne avete bisogno dopo che un oggetto file viene chiuso/garbage collection
  • mentre lavorando con la struttura C, regolare il conteggio dei riferimenti dell'oggetto corrispondente con PyFile_IncUseCount()/.
  • garantire nessun altro di I/O sui descrittori oggetti/file in quanto sarebbe rovinare i dati (ad esempio sin da chiamare iter(f)/for l in f, la cache interna è fatto che è indipendente dal caching stdio s')
+0

Se siete preoccupati per la libreria che utilizza un diverso runtime C (principalmente un problema di Windows), quindi usare 'PyFile_AsFile' non risolve nulla e limita il codice a Python 2 senza una buona ragione. Perché portare Cython nella discussione? Questo è un seguito casuale. – eryksun

+0

Inoltre, non passare mai 'id (f)' come puntatore. Vuoi 'py_object (f)' per passare un oggetto Python - come 'PyObject *' per CPython. L'uso di 'id' per ottenere un indirizzo di base è specifico per CPython, e il passaggio degli interi Python come * argomenti * è anch'esso predefinito come valore int convertito a 32 bit C, che troncherà un valore puntatore a 64 bit. – eryksun

+0

Mi piacerebbe vedere un po 'di supporto per "troncare i puntatori ai numeri interi". Python ha una nozione di interi lunghi, lo sai, e non c'è assolutamente alcun motivo per troncare un 'c_void_p'. –

Problemi correlati