2013-05-19 8 views
7

Vorrei chiamare le mie funzioni C all'interno di una libreria condivisa dagli script Python. I problemi si verificano quando si passano i puntatori, gli indirizzi a 64 bit sembrano essere troncati a indirizzi a 32 bit all'interno della funzione chiamata. Sia Python che la mia libreria sono a 64 bit.Python sta passando l'indirizzo del puntatore a 32 bit alle funzioni C

I seguenti codici di esempio illustrano il problema. Lo script Python stampa l'indirizzo dei dati passati alla funzione C. Quindi, l'indirizzo ricevuto viene stampato dall'interno della funzione C chiamata. Inoltre, la funzione C dimostra che è a 64 bit stampando la dimensione e l'indirizzo della creazione locale di memoria. Se il puntatore viene utilizzato in qualsiasi altro modo, il risultato è un segfault.

CMakeLists.txt

cmake_minimum_required (VERSION 2.6) 
add_library(plate MODULE plate.c) 

plate.c

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

void plate(float *in, float *out, int cnt) 
{ 
    void *ptr = malloc(1024); 
    fprintf(stderr, "passed address: %p\n", in); 
    fprintf(stderr, "local pointer size: %lu\n local pointer address: %p\n", sizeof(void *), ptr); 
    free(ptr); 
} 

test_plate.py

import numpy 
import scipy 
import ctypes 

N = 3 
x = numpy.ones(N, dtype=numpy.float32) 
y = numpy.ones(N, dtype=numpy.float32) 
plate = ctypes.cdll.LoadLibrary('libplate.so') 

print 'passing address: %0x' % x.ctypes.data 
plate.plate(x.ctypes.data, y.ctypes.data, ctypes.c_int(N)) 

uscita pitone-2,7

In [1]: eseguire ../test_plate.py

indirizzo passando: 7f9a09b02320

indirizzo superato: 0x9b02320

dimensione del puntatore locale: 8

locale indirizzo del puntatore: 0x7f9a0949a400

risposta

8

Il problema è che il modulo ctypes non controlla la firma della funzione della funzione che si sta tentando di chiamare. Invece, basa le tipologie C sui tipi Python, quindi la linea ...

plate.plate(x.ctypes.data, y.ctypes.data, ctypes.c_int(N)) 

... sta passando i primi due params come interi. Vedere la risposta eryksun per il motivo per cui vengono troncati a 32 bit.

Per evitare il troncamento, è necessario dire ctypes che questi parametri sono in realtà puntatori con qualcosa di simile ...

plate.plate(ctypes.c_void_p(x.ctypes.data), 
      ctypes.c_void_p(y.ctypes.data), 
      ctypes.c_int(N)) 

... anche se ciò che sono in realtà puntatori - è un altro importa - non possono essere puntatori a float come si assume il codice C.


Aggiornamento

eryksun da allora ha pubblicato una risposta molto più completo per l'esempio SPECIFICI numpy in questa domanda, ma lascio questo qui, dal momento che potrebbe essere utile nel caso generale di troncamento del puntatore per programmatori che utilizzano qualcosa di diverso da numpy.

+0

@eryksun Windows 'long' ancora solo 32 bit su Windows a 64 bit? – Aya

+2

Su Windows a 64 bit un 'long' è a 32 bit e un' long long' è a 64 bit. – eryksun

+0

@eryksun Strano, ma suppongo che abbia il pregio di essere coerente. – Aya

2

Se non si indicano ai tipi che tipo sono i parametri, tenta di dedurlo dai valori che si passano alla funzione. E questa deduzione non funzionerà sempre come tu hai bisogno.

Il modo consigliato per risolvere questo problema è impostare l'attributo argtypes della funzione e indicare esplicitamente a ctypes quali sono i tipi di parametro.

plate.plate.argtypes = [ 
    ctypes.POINTER(ctypes.c_float), 
    ctypes.POINTER(ctypes.c_float), 
    ctypes.c_int 
] 

Quindi è possibile richiamare la funzione in questo modo:

plate.plate(x.ctypes.data, y.ctypes.data, N) 
+0

@eryksun Personalmente penso che l'informazione varrebbe la pena includere in una risposta. Penso che "argtypes" sia una soluzione migliore della risposta attualmente accettata. Ma chiaramente non conosco abbastanza bene i dettagli, soprattutto per 'numpy' che è chiaramente un po 'speciale. Saresti in grado di aggiungere una risposta. Quindi cancellerei questo e aumenterò la tua risposta accurata. –

+0

Ho un problema simile all'aspetto, ma non correlato a numpy. Nel mio caso tutti i prototipi di funzione sono definiti ma non aiutano. I miei puntatori a 64 bit sono ancora troncati a 32 bit :-( – kriss

+0

ok, mio ​​male: ho scoperto perché il mio puntatore è stato troncato nel mio caso Il prototipo della funzione che riceveva il puntatore era ok, ma avevo problemi con l'altro C funzione chiamata dal ctype che alloca quel puntatore.Il tipo di ritorno di quella funzione deve essere definito usando 'restype = c_void_p' o un altro puntatore o restituisce i default di default come int. Nel mio caso ho fornito il restype, ma aggiungo un refuso (un finale" s "dopo il restype) ed è stato ignorato.La parte fastidiosa è che può passare inosservata a volte poiché funziona solo se gli indirizzi nella memoria insufficiente vengono utilizzati dalla libreria. – kriss

5

Python di PyIntObject utilizza un C long internamente, che è a 64 bit sulla maggior parte delle piattaforme a 64 bit (ad esclusione di Windows a 64 bit). Tuttavia, ctypes assegna il risultato convertito a pa->value.i, dove value è un unione e il campo i è un valore di 32 bit int. Per i dettagli, vedere ConvParam in Modules/_ctypes/callproc.c, righe 588-607 e 645-664. ctypes è stato sviluppato su Windows, dove uno long è sempre a 32 bit, ma non so perché questo non è stato modificato per utilizzare invece il campo long, ad esempio pa->value.l. Probabilmente, per la maggior parte del tempo è più conveniente creare una C int invece di utilizzare l'intera gamma di long.

In ogni caso, ciò significa che non è possibile passare semplicemente un Python int per creare un puntatore a 64 bit. Devi creare esplicitamente un puntatore ctypes. Hai un numero di opzioni per questo. Se non sei preoccupato per la sicurezza del tipo, l'opzione più semplice per un array NumPy è utilizzare l'attributo ctypes. Questo definisce il gancio _as_parameter_ che consente agli oggetti Python di impostare il modo in cui vengono convertiti nelle chiamate di funzioni dei tipi (vedere le linee 707-719 nel collegamento precedente). In questo caso crea un void *. Ad esempio, si potrebbe definire plate come questo:

plate.plate(x.ctypes, y.ctypes, N) 

Tuttavia, questo non offre alcun tipo di sicurezza per evitare che la funzione di essere chiamato con una serie di tipo errato, che si tradurrà in entrambi i nonsense, bug, o un errore di segmentazione. np.ctypeslib.ndpointer risolve questo problema. Questo crea un tipo personalizzato che è possibile utilizzare nell'impostazione di argtypes e restype di un puntatore a funzione ctypes. Questo tipo può verificare il tipo di dati dell'array, il numero di dimensioni, la forma e i flag. Per esempio:

import numpy as np 
import ctypes 

c_npfloat32_1 = np.ctypeslib.ndpointer(
    dtype=np.float32, 
    ndim=1, 
    flags=['C', 'W']) 

plate = ctypes.CDLL('libplate.so') 

plate.plate.argtypes = [ 
    c_npfloat32_1, 
    c_npfloat32_1, 
    ctypes.c_int, 
] 

N = 3 
x = np.ones(N, dtype=np.float32) 
y = np.ones(N, dtype=np.float32) 

plate.plate(x, y, N) # the parameter is the array itself 
+1

+1 per una soluzione migliore della mia :) – Aya

1

In realtà, è necessario impostare plate.argstype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int], e allora sarà ok per accettare l'indirizzo in c func in pitone.
Ho incontrato il problema e l'ho risolto come dicevo.

Problemi correlati