2010-07-14 3 views
2

Je tente d'encapsuler une bibliothèque C à l'aide de ctypes. Une caractéristique de la bibliothèque est un callback ondestroy qui est appelé quand un handle renvoyé par la bibliothèque est sur le point d'être détruit.Transmutation d'un objet ctypes.py_object dans un rappel

La fonction de rappel a la signature:

void cb(f *beingdestroyed); 

L'API permet d'associer un spécifié par l'utilisateur avec void * f quand il est retourné par la bibliothèque. Par conséquent, je peux associer le py_object utilisé pour l'envelopper en tant que données utilisateur. Mon plan est d'avoir un champ is_valid et quand le callback est déclenché pour extraire les user_data et mettre ce champ à false.

Mon problème est de savoir comment extraire mon haut niveau py_object; Je peux récupérer les données de l'utilisateur en tant que ctypes.void_p et les convertir en ctypes.py_object mais je n'ai que l'API Python C avec laquelle travailler. Il est possible de retourner vers un objet de haut niveau avec lequel je peux travailler en écrivant user_object.is_valid = 0?

Répondre

3

Pour des précisions sur la réponse de Thomas Heller:

  • Le prototype de rappel devrait spécifier c_void_p pour le paramètre de contexte
  • Les argTypes pour la fonction de la bibliothèque doit spécifier py_object pour le paramètre de contexte
  • Cette fonction devrait être appelé avec py_object(my_python_context_object)
  • Votre implémentation Python de la fonction de rappel doit convertir le contexte en objet py_object et en extraire la valeur: cast(context, py_object).value

Voici un exemple de travail. Commencez par source C pour une DLL simple:

// FluffyBunny.c 
// Compile on windows with command line 
//  cl /Gd /LD FluffyBunny.c 
// Result is FluffyBunny.DLL, which exports one function: 
//  FluffyBunny() uses __cdecl calling convention. 

#include <windows.h> 

BOOL APIENTRY DllMain(HMODULE, DWORD, LPVOID) { 
    return TRUE; 
} 

typedef int (*FLUFFYBUNNY_CALLBACK)(void *context); 

__declspec(dllexport) int FluffyBunny(FLUFFYBUNNY_CALLBACK cb, void *context) { 
    int result = 0; 
    int count = 0; 
    if (cb) { 
    while (result == 0) { 
     result = (*cb)(context); 
     ++count; 
    } 
    } 
    return count; 
} 

Et voici un programme Python qui appelle la DLL:

# FluffyBunny.py 
from ctypes import * 

# Declare a class that will be used for context info in calls to FluffyBunny() 
class Rabbit: 
    def __init__(self): 
     self.count = 0 

# FluffyBunny() wants a callback function with the following C prototype: 
#  typedef int (*FLUFFYBUNNY_CALLBACK)(void *context); 
FLUFFYBUNNY_CALLBACK = CFUNCTYPE(c_int, c_void_p) 

# This DLL has been compiled with __cdecl calling convention. 
FluffyBunny_dll = CDLL('FluffyBunny.dll') 

# Get the function from the library. Its C prototype is: 
#  int FluffyBunny(FLUFFYBUNNY_CALLBACK cb, void *context); 
# Note that I use "py_object" instead of "c_void_p" for the context argument. 
FluffyBunny   = FluffyBunny_dll.FluffyBunny 
FluffyBunny.restype = c_int 
FluffyBunny.argtypes = [FLUFFYBUNNY_CALLBACK, py_object] 

# Create Python version of the callback function. 
def _private_enumerateBunnies(context): 
    # Convert the context argument to a py_object, and extract its value. 
    # This gives us the original Rabbit object that was passed in to 
    # FluffyBunny(). 
    furball = cast(context, py_object).value 
    # Do something with the context object. 
    if furball: 
     furball.count += 1 
     print 'furball.count =', furball.count 
     # Return non-zero as signal that FluffyBunny() should terminate 
     return 0 if (furball.count < 10) else -1 
    else: 
     return -1 

# Convert it to a C-callable function. 
enumerateBunnies = FLUFFYBUNNY_CALLBACK(_private_enumerateBunnies) 

# Try with no context info. 
print 'no context info...' 
result = FluffyBunny(enumerateBunnies, None) 
print 'result=', result 

# Give it a Python object as context info. 
print 'instance of Rabbit as context info...' 
furball = Rabbit() 
result = FluffyBunny(enumerateBunnies, py_object(furball)) 
print 'result=', result 
+0

Merci, c'était exactement ce dont j'avais besoin. –

1

La manière habituelle est d'éviter complètement ce problème.

Utilisez une méthode au lieu d'une fonction de rappel et le self implicite permettra d'accéder à votre champ user_data.

1

J'ai appliqué les informations contenues dans la réponse de Bob Pyron à mon code, mais n'a pas comme ça le rappel devait être conscient qu'il était appelé par un programme C, et traiter des trucs ctypes. Il s'avère qu'avec un changement mineur, _private_enumerateBunnies() obtient un objet python au lieu d'un void *. Je l'ai fait l'équivalent de cela dans mon code:

FLUFFYBUNNY_CALLBACK = CFUNCTYPE (c_int, py_object)

Et puis tous les trucs-C funky était caché dans le code qui avait déjà traiter avec, et il ne s'est pas étendu aux utilisateurs de cette API (c'est-à-dire, les fournisseurs de rappels). Bien sûr, vous devez lui passer un objet python, mais c'est une restriction plutôt mineure.

J'ai beaucoup bénéficié de la réponse originale de Bob (une fois que j'ai passé tous les lapins), alors merci!

Questions connexes