2015-04-14 1 views
3

J'essaie de concevoir une interface C qui pourrait facilement être étendue en Python (en utilisant ctypes). Je l'ai utilisé le natural idiom dans C:Classe auto-référencée: classe Python concrète de l'interface C

struct format { 
    int (*can_open)(const char *filename); 
    struct format * (*open)(const char *filename); 
    void (*delete)(struct format *self); 
    int (*read)(struct format *self, char *buf, size_t len); 
}; 

Il fonctionne très bien si je veux étendre cette interface de C directement:

struct derived /* concrete implementation */ 
{ 
    struct format base; 
}; 

Mais ce que je voudrais vraiment faire, est de mettre en œuvre cette interface à partir de Python en utilisant ctypes. Voici ce que j'ai jusqu'à présent:

CANOPENFUNC = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p) 
#OPENFUNC  = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_char_p) 
#OPENFUNC  = ctypes.CFUNCTYPE(ctypes.POINTER(python_format), ctypes.c_char_p) 
#DELETEFUNC = ctypes.CFUNCTYPE(None, ctypes.c_void_p) 
#READFUNC  = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p) 

def py_canopen_func(string): 
    print "py_canopen_func", string 
    return 1 

canopen_func = CANOPENFUNC(py_canopen_func) 
#open_func  = OPENFUNC( py_open_func) 
#delete_func = DELETEFUNC(py_canopen_func) 
#read_func  = READFUNC(py_canopen_func) 

class python_format(ctypes.Structure): 
    _fields_ = (
    ('can_open', CANOPENFUNC), 
    ('open',  OPENFUNC), 
    ('delete', DELETEFUNC), 
    ('read',  READFUNC), 
) 
    def __init__(self): 
    self.can_open = canopen_func 
    OPENFUNC = ctypes.CFUNCTYPE(ctypes.POINTER(python_format), ctypes.c_char_p) 
    def py_open_func2(string): 
     print "py_open_func2", string 
     return ctypes.byref(self) 
    self.open = OPENFUNC(py_open_func2) 
    #self.delete = delete_func 
    #self.read = read_func 

Vraiment, je me bats pour définir le prototype de OPENFUNC ici. Techniquement, il devrait être:

OPENFUNC = ctypes.CFUNCTYPE(ctypes.POINTER(python_format), ctypes.c_char_p) 

Cependant, je dois définir python_format d'abord, qui à son tour nécessite une définition pour OPENFUNC.

Point bonus: qu'est-ce qu'une implémentation de fonction réelle? Par exemple:

def func(str): return None 

ou

def func(str): i = python_format(); return ctypes.pointer(i) 

deux me donne:

class python_format(ctypes.Structure): 
    pass 
OPENFUNC = ctypes.CFUNCTYPE(ctypes.POINTER(python_format), ctypes.c_char_p) 
OPENFUNC(func) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: invalid result type for callback function 

Est-ce lié à cette autre issue? Si oui, devrais-je changer ma conception C initiale, puisque je ne serai pas capable de retourner un pointeur vers une instance python_format à partir d'un rappel?

+0

correcte. 'open' est destiné à renvoyer l'instance allouée. Idéalement, l'implémentation de 'open' en python, devrait être simple' return self' ou 'return ctypes.pointer (self)'. 'delete' sera un no-op en python à cause du garbage collector. – malat

+1

Le message 'TypeError' que vous obtenez lorsque vous essayez d'utiliser un type non-simple à la suite d'un rappel est moins utile. Un type de résultat de callback doit avoir un 'setfunc' dans son' StgDictObject' (une extension ctypes du 'PyDictObject' normal). Cette exigence vous limite à l'utilisation d'un type simple tel que 'c_void_p', et la fonction de rappel doit renvoyer' ctypes.addressof (self) '. – eryksun

Répondre

1

Afin d'avoir un canonique Pour répondre à cette question, je vais répondre à ma propre question, grâce aux conseils de @eryksun. Donc, tout d'abord, bien que cela ne soit pas clair à partir du documentation, on ne peut pas retourner un type complexe à partir d'une fonction de rappel.Par conséquent, on ne peut pas mapper un pointeur de fonction C:

struct format { 
    struct format * (*open)(const char *filename); 
}; 

à

class python_format: 
    pass 
OPENFUNC = ctypes.CFUNCTYPE(ctypes.POINTER(python_format), ctypes.c_char_p) 
def py_canopen_func(string): 
    return None 
open_func  = OPENFUNC(py_open_func) 

Le code ci-dessus compile gracieusement, mais lors de l'exécution, on obtient:

Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: invalid result type for callback function 

La réponse longue est que:

Le message TypeError que vous obtenez w Si vous essayez d'utiliser un type non-simple comme le résultat d'un rappel est moins qu'utile. Le résultat d'un callback doit avoir un setfunc dans son StgDictObject (une extension ctypes de le PyDictObject normal). Cette exigence vous limite à l'aide d'un type simple tel que c_void_p [...]

Par conséquent, la seule solution ici, à partir d'aujourd'hui, jusqu'à ce que issue 5710 est fixé est le suivant:

class python_format(ctypes.Structure): 
    __self_ref = [] 
    def __init__(self): 
    self.open  = self.get_open_func() 
    # technically should be a @classmethod but since we are self-referencing 
    # ourself, this is just a normal method: 
    def get_open_func(self): 
    def py_open_func(string): 
     python_format.__self_ref.append(self) 
     return ctypes.addressof(self) 
    return OPENFUNC(py_open_func) 
OPENFUNC  = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_char_p) 

# delay init required because `read_info` requires a forward declaration: 
python_format._fields_ = (
    ('open',  OPENFUNC), 
) 
+0

L'affectation à '_fields_' ne fonctionne pas après son utilisation. Et il semble que les règles ont changé w.r.t. cette –

1

Dans le documentaiton pour ctypes.Structure il explique comment faire:

Il est possible de définir la variable After classe _fields_ la déclaration de classe qui définit la sous-classe de la structure, ce qui permet de créer données types qui se font référence directement ou indirectement

Cela signifie que vous pouvez ajouter un:

class python_format(ctypes.Structure): # forward declaration 
    pass 

puis après avoir défini OPENFUNC (et les autres types de fonction):

OPENFUNC = ctypes.CFUNCTYPE(ctypes.POINTER(python_format), ctypes.c_char_p) 
DELETEFUNC = etc... 

pouvoir définir ensuite python_format._fields_ ainsi:

python_format._fields_ = (
    ('can_open', CANOPENFUNC), 
    ('open',  OPENFUNC), 
    ('delete', DELETEFUNC), 
    ('read',  READFUNC), 
) 

Voici un exemple plus complet basé sur votre code:

import ctypes 

class python_format(ctypes.Structure): # forward declaration 
    pass 

CANOPENFUNC = ctypes.PYFUNCTYPE(ctypes.c_int, ctypes.c_char_p) 
OPENFUNC = ctypes.PYFUNCTYPE(ctypes.c_int, 
           ctypes.POINTER(python_format), 
           ctypes.c_char_p) 
DELETEFUNC = ctypes.PYFUNCTYPE(None, ctypes.c_void_p) 
READFUNC = ctypes.PYFUNCTYPE(ctypes.c_int, ctypes.c_void_p) 

def py_canopen_func(self, string): 
    print "py_canopen_func", string 
    return 1 

def py_open_func(self, string): 
    print "py_open_func2", string 
    # Return types from callbacks cannot be anything other than simple 
    # datatypes (c_int, c_float, ..., c_void_p). For other datatypes 
    # (STRUCTURE, POINTER, ...), ctypes returns the following error 
    # "Invalid result type for callback function" 
    # see http://bugs.python.org/issue5710 
    return 1 # can't return ctypes.byref(self) 

canopen_func = CANOPENFUNC(py_canopen_func) 
open_func = OPENFUNC(py_open_func) 
#delete_func = DELETEFUNC(py_canopen_func) 
#read_func = READFUNC(py_canopen_func) 

class python_format(ctypes.Structure): 
    python_format._fields_ = (
     ('can_open', CANOPENFUNC), 
     ('open', OPENFUNC), 
     ('delete', DELETEFUNC), 
     ('read', READFUNC), 
    ) 

    def __init__(self): 
     self.can_open = canopen_func 
     self.open = open_func 
     #self.delete = delete_func 
     #self.read = read_func 

pf = python_format() 
+0

Pourriez-vous donner un exemple d'une telle fonction python à utiliser 'OPENFUNC'? – malat

+0

Il n'y a aucune raison pour que ces rappels contiennent le GIL. Avant de rappeler en Python, ctypes ré-acquiert le GIL. Vous êtes libre d'utiliser, et vous devriez vraiment utiliser, "CFUNCTYPE" ici. En outre, selon mon commentaire ci-dessus, le type de résultat 'OPENFUNC' peut être défini sur' c_void_p', pour lequel le rappel retournera 'ctypes.addressof (self)'. – eryksun