Swig de Python – crea una instancia envuelta de swig desde el puntero de ctypes

Tengo código C ++ con una clase envuelta con swig. No puedo modificar el código o el envoltorio. En Python, tengo, usando ctypes, un puntero a una instancia de dicha clase de C ++. ¿Cómo puedo crear un envoltorio de swig alrededor de este puntero?

Sé que los objetos swig tienen un atributo ‘this’ que apunta internamente al objeto envuelto, pero no pude encontrar una forma de establecerlo en el puntero que tengo a mano.

¡Gracias por la ayuda!

Puede hacer esto, pero es mucho trabajo y sería mucho más sencillo solucionar el problema subyacente de hacer que los ctypes o la interfaz SWIG sean completos y utilizables que la intercambiabilidad forzada. (También vale la pena señalar que es más fácil crear un objeto ctypes a partir de un objeto SWIG que hacer lo que estás tratando de hacer, que es crear un objeto SWIG a partir de uno ctypes).

Para ilustrar esto, he creado el siguiente archivo de encabezado que envolveremos con SWIG:

struct Foo { double d; char msg[20]; }; 

Luego lo envolví con la siguiente interfaz:

 %module test %{ #include "test.h" %} // Prove I'm not cheating here - we can't even instantiate this class %nodefaultctor; %include "test.h" 

También agregué una función de prueba para que podamos llamar desde ctypes, que no está envuelto en SWIG:

 #include "test.h" // This function returns a Foo, but is only accessible to ctypes extern "C" Foo *fun1() { static Foo f = { 1.234, "Hello" }; return &f; } 

Su suposición sobre este atributo de las clases envueltas de SWIG es un buen punto de partida, pero no es tan simple como cambiarlo: el tipo de objeto en el que se inserta tiene que coincidir con lo que SWIG espera. Es más que un puntero representado como un int:

 repr(test.Foo().this) # You'll need to drop the nodefaultctor directive to see this "" 

Si inspeccionamos el código fuente que genera SWIG, podemos ver que hay una función que toma un puntero, información de tipo y crea estos objetos para nosotros:

 SWIGRUNTIME PyObject * SwigPyObject_New(void *ptr, swig_type_info *ty, int own); 

SWIGRUNTIME el hecho de que SWIGRUNTIME está definido como static por defecto por ahora, para que SWIGRUNTIME a experimentar con este tiempo de ejecución, podemos redefinirlo a extern . Más adelante veremos soluciones para cuando no podamos hacer eso.

Por lo tanto, nuestro objective es tomar la salida de la función “ctypes only” y pasarla, a través de más llamadas de SwigPyObject_New a SwigPyObject_New para crear algo que podamos intercambiar con el atributo de este módulo SWIG.

Para llamar así, normalmente llamaríamos a SWIG_TypeQuery para buscar el swig_type_info correcto para usar. Sin embargo, esto es en realidad una macro, que se expande para pasar en algunas variables estáticas que siempre son estáticas. Así que en lugar de eso usaremos esta función:

 SWIGRUNTIME swig_type_info * SWIG_Python_TypeQuery(const char *type) 

(Con la misma condición de SWIGRUNTIME ).

En este punto, tenemos lo suficiente como para poder intercambiar este atributo de un objeto sustituto y hacerlo si pudiéramos construir donantes. (Aunque eso se filtraría). Hay dos maneras en que podemos hacer esto mejor:

  1. Monkey patch __init__ inside test.Foo para trabajar. Esto es mejor si realmente tiene %nodefaultctor dentro de la interfaz SWIG que no desea recomstackr:

     def patched_init(self, ptr): self.this = ptr test.Foo.__init__ = patched_init 
  2. Cree una nueva clase que solo tenga un __init__ que establezca this atributo antes de modificar el atributo __class__ y use eso en su lugar:

     class FooCtypesSwigInterop(object): def __init__(self, ptr): self.this = ptr self.__class__ = test.Foo 

    esta opción tiene más sentido cuando no quiere interrumpir la test.Foo . La implementación existente de __init__ .

Dicho esto, ahora podemos lograr nuestro objective inicial con algo como esto:

 import ctypes import test # This *must* happen after the import of the real SWIG module # 0x4 is RTLD_NOLOAD which ensures that we get the same handle as the Python # import did, even if it was loaded with RTLD_LOCAL as Python is prone to. swig_module = ctypes.PyDLL('./_test.so',ctypes.RTLD_LOCAL|0x4) # Note that we used PyDLL instead of CDLL because we need to retain the GIL # Setup SWIG_Python_TypeQuery to have the right argument/return types # (Using void* as a substitute for swig_type_info*) SWIG_Python_TypeQuery = swig_module.SWIG_Python_TypeQuery SWIG_Python_TypeQuery.argtypes = [ctypes.c_char_p] SWIG_Python_TypeQuery.restype = ctypes.c_void_p # Likewise SwigPyObject_New, using ctypes.py_object though for return SwigPyObject_New = swig_module.SwigPyObject_New SwigPyObject_New.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int] SwigPyObject_New.restype = ctypes.py_object # Actually do the type query for the type our ctypes function returns: SWIGTYPE_p_Foo = SWIG_Python_TypeQuery('Foo*') print(hex(SWIGTYPE_p_Foo)) # Now the ctypes function itself that returns the type we want SWIG managed fun1 = swig_module.fun1 fun1.argtypes = [] fun1.restype = ctypes.c_void_p # Make the actual ctypes call we care about here result = fun1() print(hex(result)) # And then create a SwigPyObject for it from the void* return type # Note that 0 means not owned by SWIG sresult = SwigPyObject_New(result, SWIGTYPE_p_Foo, 0) print(repr(sresult)) # This is how we jimmy it back into the this attribute of a SWIG type class FooCtypesSwigInterop(object): def __init__(self, ptr): self.this = ptr self.__class__ = test.Foo c = FooCtypesSwigInterop(sresult) # Finally a usable SWIG object from the ctypes call print(c.msg) 

Todo esto se comstack y trabaja con:

 swig3.0 -python -c++ -Wall test.i g++ -shared -o _test.so test_wrap.cxx fun1.cc -Wall -Wextra -fPIC -I/usr/include/python2.7/ -std=c++11 -DSWIGRUNTIME=extern LD_LIBRARY_PATH=. python run.py 

Y nos da:

 0x7fb6eccf29e0 0x7fb6eccf2640  Hello 

Para solucionar el problema de tener SWIGRUNTIME definido como static , deberá realizar un paso más. Utilice símbolos de depuración o realice una ingeniería inversa del módulo binario SWIG que tiene, pero no puede modificarlo para encontrar las direcciones de las dos funciones que necesitamos que no se exportan en relación con un símbolo exportado. Luego puede usarlos para construir punteros de función ctypes en lugar de buscarlos por nombre. Por supuesto, sería más fácil comprar / encontrar / reescribir el módulo SWIG, o agregar las características faltantes a la interfaz de ctypes probablemente.

(Finalmente, vale la pena señalar que, aunque no parece aplicarse aquí si SWIG se ejecuta con -builtin , será necesario -builtin algunos cambios sustanciales para que esta respuesta funcione).