2013-05-19 10 views
7

Ich möchte meine C-Funktionen in einer gemeinsam genutzten Bibliothek aus Python-Skripten aufrufen. Probleme beim Übergeben von Zeigern, die 64-Bit-Adressen scheinen innerhalb der aufgerufenen Funktion auf 32-Bit-Adressen abgeschnitten zu sein. Python und meine Bibliothek sind 64bit.Python übergibt 32-Bit-Zeigeradresse an C-Funktionen

Die folgenden Beispielcodes veranschaulichen das Problem. Das Python-Skript gibt die Adresse der Daten aus, die an die C-Funktion übergeben werden. Dann wird die empfangene Adresse aus der aufgerufenen C-Funktion gedruckt. Darüber hinaus beweist die C-Funktion, dass es 64-Bit ist, indem die Größe und die Adresse des lokal erstellten Speichers gedruckt werden. Wenn der Zeiger auf andere Weise verwendet wird, ist das Ergebnis ein 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)) 

Output aus Python-2,7

in [1]: run ../test_plate.py

vorbei Adresse: 7f9a09b02320

weitergegeben Adresse: 0x9b02320

lokale Zeigergröße: 8

lokale Pointer-Adresse: 0x7f9a0949a400

Antwort

8

Das Problem ist, dass das Modul ctypes die Funktionssignatur der Funktion nicht überprüft, die Sie aufrufen möchten. Statt dessen stützt sich die C-Typen auf dem Python-Typen, so dass die Linie ...

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

... ist, vorbei an den die ersten zwei params als ganze Zahlen. Siehe eryksun 's Antwort für den Grund, warum sie auf 32 Bits abgeschnitten werden.

das Abschneiden zu vermeiden, müssen Sie ctypes, dass dieser params mit etwas tatsächlich Zeiger ist sagen, wie ...

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

... obwohl sie eine andere eigentlich das, was sind die Zeiger - ist Wichtig - sie sind möglicherweise keine Zeiger auf float, wie Ihr C-Code annimmt.


aktualisiert

eryksun hat eine viel vollständigere Antwort für das numpy -spezifische Beispiel in dieser Frage seit geschrieben, aber ich werde das hier verlassen, da es in der nützlich sein könnte allgemeiner Fall der Zeigerabkürzung für Programmierer, die etwas anderes als numpy verwenden.

+0

@eryksun Ist ein Windows 'long' immer noch nur 32-Bit auf 64-Bit Windows? – Aya

+2

Auf 64-Bit-Windows ist ein 'Long' 32-Bit und ein' Long-Long' ist 64-Bit. – eryksun

+0

@eryksun Seltsam, aber ich denke, es hat die Tugend, konsequent zu sein. – Aya

2

Wenn Sie ctypes nicht mitteilen, welchen Typ die Parameter haben, wird versucht, sie aus den Werten abzuleiten, die Sie an die Funktion übergeben. Und diese Schlussfolgerung wird nicht immer so funktionieren, wie Sie es brauchen.

Der empfohlene Weg, um damit umzugehen, ist, das argtypes Attribut der Funktion zu setzen und so ausdrücklich ctypes zu sagen, was die Parametertypen sind.

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

Dann können Sie die Funktion wie folgt aufrufen:

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

@eryksun Persönlich denke ich, dass Informationen wäre es wert, in einer Antwort enthalten. Ich denke, "Argtypes" ist eine bessere Lösung als die Antwort, die derzeit akzeptiert wird. Aber klar, ich kenne die Details nicht gut genug, vor allem für "numpy", was eindeutig ein wenig speziell ist. Könnten Sie eine Antwort hinzufügen? Dann würde ich dies löschen und Ihre genaue Antwort upvote. –

+0

Ich habe ein ähnlich aussehendes Problem, aber nicht im Zusammenhang mit numpy. In meinem Fall sind alle Funktionsprototypen definiert, aber es hilft nicht. Meine 64-Bit-Zeiger sind immer noch auf 32 Bit abgeschnitten :-( – kriss

+0

ok, mein Fehler: Ich habe herausgefunden, warum der Zeiger in meinem Fall abgeschnitten wurde. Der Prototyp der Funktion, die den Zeiger empfängt, war in Ordnung, aber ich hatte Probleme mit dem anderen C Funktion, die von ctype aufgerufen wird und diesen Zeiger zuweist.Der Typ der Rückgabe dieser Funktion muss definiert werden, indem 'restype = c_void_p 'oder der Zeiger des Zeigers oder des Rückgabetyps als int verwendet wird. In meinem Fall habe ich einen Restyp angegeben, aber ich füge einen Tippfehler hinzu. s "nach dem restype) und es wurde ignoriert. Der nervige Teil ist, dass es manchmal unbemerkt bleiben kann, da es funktioniert, solange nur Adressen in wenig Speicher von der Bibliothek verwendet werden. – kriss

5

Pythons PyIntObject verwendet eine C long intern, die auf den meisten 64-Bit-Plattformen 64-Bit ist (mit Ausnahme von 64-Bit-Windows). Ctypes ordnet das konvertierte Ergebnis jedoch pa->value.i zu, wobei value eine Union und das i-Feld ein 32-Bit int ist. Für Einzelheiten siehe ConvParam in Modules/_ctypes/callproc.c, Zeilen 588-607 und 645-664. ctypes wurde unter Windows entwickelt, wobei long immer 32-Bit ist, aber ich weiß nicht, warum dies nicht geändert wurde, stattdessen das Feld long zu verwenden, d. h. pa->value.l. Wahrscheinlich ist es in den meisten Fällen einfacher, einen C int zu erstellen, anstatt den gesamten Bereich des long zu verwenden.

Wie auch immer, das bedeutet, Sie können nicht einfach einen Python int übergeben, um einen 64-Bit-Zeiger zu erstellen. Sie müssen explizit einen Ctypes-Zeiger erstellen. Sie haben eine Reihe von Optionen dafür. Wenn Sie sich nicht mit der Typensicherheit befassen, ist die einfachste Option für ein NumPy-Array die Verwendung des Attributs ctypes. Dies definiert den Hook _as_parameter_, mit dem Python-Objekte festlegen können, wie sie in Ctypes-Funktionsaufrufen konvertiert werden (siehe Zeilen 707-719 im vorherigen Link). In diesem Fall wird void * erstellt. Zum Beispiel, würden Sie plate wie folgt aufrufen:

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

Allerdings bedeutet dies keine Art Sicherheit bieten die Funktion von dazu aufgerufen, mit einer Reihe von falschen Art zu verhindern, die in jedem Unsinn führen, Fehler oder ein Segmentierungsfehler. np.ctypeslib.ndpointer löst dieses Problem. Dadurch wird ein benutzerdefinierter Typ erstellt, den Sie zum Festlegen der argtypes und restype eines Ctypes-Funktionszeigers verwenden können. Dieser Typ kann den Datentyp des Arrays, die Anzahl der Dimensionen, die Form und die Flags überprüfen. Zum Beispiel:

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 für eine bessere Lösung als meine :) – Aya

1

Eigentlich sollen Sie setzen plate.argstype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int], und dann wird es in Ordnung sein, um die Adresse in c func von Python zu akzeptieren.
Ich habe das Problem und ich löste es als was ich sage.

Verwandte Themen