tutorial tablas menus listas etiquetas español ejemplos con bordes java jni swig

tablas - Cómo envolver rellamadas en Java con SWIG



swing java tutorial español (1)

Siguiendo este hilo: ¿Cómo debo escribir el archivo .i para envolver las devoluciones de llamadas en Java o C #

Me doy cuenta de que mi pregunta es similar, pero la respuesta a esa pregunta se adaptó específicamente para el argumento de datos de usuario void* , mientras que mi devolución de llamada toma un enum y un char* .

Así es como mi devolución de llamada se define y usa en mi archivo de cabecera

typedef void (*Callback_t)(const Log SomeLog, const char *Text); virtual void SetCallback(const Callback_t SomeCallback, const Log SomeLog) = 0;

donde Log es una enumeración.

Soy bastante nuevo para JNI y SWIG, por lo que necesitaría una guía específica sobre cómo ajustar esto, similar a la presentada en el hilo que mencioné anteriormente.

Gracias por adelantado.


La solución simple es adaptar mi respuesta a la pregunta anterior para simplemente usar una variable global para almacenar el jobject que no podemos almacenar dentro de algún argumento que se nos pasa durante la devolución de llamada. (Es un mal diseño por parte del autor de la biblioteca que no parece haber una manera de pasar un argumento cuando se establece la devolución de llamada que está disponible para la función, en el momento en que ocurre una devolución de llamada. Normalmente eso es void* o simplemente this . Dado que es C ++ si fuera yo el que diseñara la biblioteca, habría utilizado std::function personalmente y entonces simplemente podríamos confiar en los directores de SWIG aquí, pero eso no parece ser una opción en este escenario)

Para trabajar en esto, escribí test.h:

typedef enum { blah = 1, blahblah = 2, } Log; typedef void (*Callback_t)(const Log SomeLog, const char *Text); void SetCallback(const Callback_t SomeCallback, const Log SomeLog); void TestIt(const char *str);

y test.c:

#include "test.h" #include <assert.h> static Callback_t cb=0; static Log log=0; void SetCallback(const Callback_t SomeCallback, const Log SomeLog) { cb = SomeCallback; log = SomeLog; } void TestIt(const char *str) { assert(cb); cb(log, str); }

(Tenga en cuenta que estoy trabajando en esto simplemente como un ejercicio en C ya que la interfaz C ++ con la que debe trabajar puede ser C para nosotros aquí de todos modos).

Con eso en su lugar, puede escribir un archivo de interfaz para SWIG como:

%module test %{ #include "test.h" #include <assert.h> // NEW: global variables (bleurgh!) static jobject obj; static JavaVM *jvm; // 2: static void java_callback(Log l, const char *s) { printf("In java_callback: %s/n", s); JNIEnv *jenv = 0; // NEW: might as well call GetEnv properly... const int result = (*jvm)->GetEnv(jvm, (void**)&jenv, JNI_VERSION_1_6); assert(JNI_OK == result); const jclass cbintf = (*jenv)->FindClass(jenv, "Callback"); assert(cbintf); const jmethodID cbmeth = (*jenv)->GetMethodID(jenv, cbintf, "Log", "(LLog;Ljava/lang/String;)V"); assert(cbmeth); const jclass lgclass = (*jenv)->FindClass(jenv, "Log"); assert(lgclass); const jmethodID lgmeth = (*jenv)->GetStaticMethodID(jenv, lgclass, "swigToEnum", "(I)LLog;"); assert(lgmeth); jobject log = (*jenv)->CallStaticObjectMethod(jenv, lgclass, lgmeth, (jint)l); assert(log); (*jenv)->CallVoidMethod(jenv, obj, cbmeth, log, (*jenv)->NewStringUTF(jenv, s)); } %} // 3: %typemap(jstype) Callback_t "Callback"; %typemap(jtype) Callback_t "Callback"; %typemap(jni) Callback_t "jobject"; %typemap(javain) Callback_t "$javainput"; // 4: (modified, not a multiarg typemap now) %typemap(in) Callback_t { JCALL1(GetJavaVM, jenv, &jvm); obj = JCALL1(NewGlobalRef, jenv, $input); JCALL1(DeleteLocalRef, jenv, $input); $1 = java_callback; } %include "test.h"

En general, asigna 1-1 a la respuesta anterior , con la excepción de reemplazar la struct contiene información de devolución de llamada con variables globales y mejorar la forma en que obtenemos JNIEnv dentro de la devolución de llamada .

Con nuestro Callback.java escrito manualmente:

public interface Callback { public void Log(Log log, String str); }

Eso es suficiente para que este caso de prueba se compile y ejecute correctamente:

public class run implements Callback { public static void main(String[] argv) { System.loadLibrary("test"); run r = new run(); test.SetCallback(r, Log.blah); test.TestIt("Hello world"); } public void Log(Log l, String s) { System.out.println("Hello from Java: " + s); } }

Que funciona:

swig -Wall -java test.i gcc -Wall -Wextra -o libtest.so -shared -I/usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ -I/usr/lib/jvm/java-1.7.0-openjdk-amd64/include/linux/ test.c test_wrap.c -fPIC javac *.java && LD_LIBRARY_PATH=. java run In java_callback: Hello world Hello from Java: Hello world

Como no nos gusta usar variables globales como esta (las llamadas múltiples a SetCallback desde el lado de Java no se comportarán de la manera que esperaríamos) mi solución preferida (en un mundo puramente C) es usar libffi para generar un cierre para nosotros . Esencialmente, eso nos permite crear un nuevo puntero de función para cada devolución de llamada activa, de modo que el conocimiento de qué objeto Java se está llamando se puede pasar implícitamente cada vez que se produce una devolución de llamada. (Esto es lo que estamos luchando por resolver. Libffi tiene un ejemplo de cierres que se ajusta bastante a nuestro escenario.

Para ilustrar esto, el caso de prueba no se modifica, el archivo de interfaz SWIG se ha convertido en esto:

%module test %{ #include "test.h" #include <assert.h> #include <ffi.h> struct Callback { ffi_closure *closure; ffi_cif cif; ffi_type *args[2]; JavaVM *jvm; void *bound_fn; jobject obj; }; static void java_callback(ffi_cif *cif, void *ret, void *args[], struct Callback *cb) { printf("Starting arg parse/n"); Log l = *(unsigned*)args[0]; const char *s = *(const char**)args[1]; assert(cb->obj); printf("In java_callback: %s/n", s); JNIEnv *jenv = 0; assert(cb); assert(cb->jvm); const int result = (*cb->jvm)->GetEnv(cb->jvm, (void**)&jenv, JNI_VERSION_1_6); assert(JNI_OK == result); const jclass cbintf = (*jenv)->FindClass(jenv, "Callback"); assert(cbintf); const jmethodID cbmeth = (*jenv)->GetMethodID(jenv, cbintf, "Log", "(LLog;Ljava/lang/String;)V"); assert(cbmeth); const jclass lgclass = (*jenv)->FindClass(jenv, "Log"); assert(lgclass); const jmethodID lgmeth = (*jenv)->GetStaticMethodID(jenv, lgclass, "swigToEnum", "(I)LLog;"); assert(lgmeth); jobject log = (*jenv)->CallStaticObjectMethod(jenv, lgclass, lgmeth, (jint)l); assert(log); (*jenv)->CallVoidMethod(jenv, cb->obj, cbmeth, log, (*jenv)->NewStringUTF(jenv, s)); } %} // 3: %typemap(jstype) Callback_t "Callback"; %typemap(jtype) Callback_t "long"; %typemap(jni) Callback_t "jlong"; %typemap(javain) Callback_t "$javainput.prepare_fp($javainput)"; // 4: %typemap(in) Callback_t { $1 = (Callback_t)$input; } %typemap(javaclassmodifiers) struct Callback "public abstract class" %typemap(javacode) struct Callback %{ public abstract void Log(Log l, String s); %} %typemap(in,numinputs=1) (jobject me, JavaVM *jvm) { $1 = JCALL1(NewWeakGlobalRef, jenv, $input); JCALL1(GetJavaVM, jenv, &$2); } struct Callback { %extend { jlong prepare_fp(jobject me, JavaVM *jvm) { if (!$self->bound_fn) { int ret; $self->args[0] = &ffi_type_uint; $self->args[1] = &ffi_type_pointer; $self->closure = ffi_closure_alloc(sizeof(ffi_closure), &$self->bound_fn); assert($self->closure); ret=ffi_prep_cif(&$self->cif, FFI_DEFAULT_ABI, 2, &ffi_type_void, $self->args); assert(ret == FFI_OK); ret=ffi_prep_closure_loc($self->closure, &$self->cif, java_callback, $self, $self->bound_fn); assert(ret == FFI_OK); $self->obj = me; $self->jvm = jvm; } return *((jlong*)&$self->bound_fn); } ~Callback() { if ($self->bound_fn) { ffi_closure_free($self->closure); } free($self); } } }; %include "test.h"

Lo cual ha logrado nuestro objetivo de eliminar globales creando un cierre usando libffi. Callback ahora se ha convertido en una clase abstracta, con una combinación de componentes C y Java que la implementan. El objetivo es realmente mantener la implementación del método abstracto. Log y administrar el ciclo de vida del resto de los datos C que deben mantenerse para implementar esto. La mayor parte del trabajo de libffi se realiza dentro de una directiva de %extend SWIG, que prácticamente refleja la documentación de libffi para cierres. La función java_callback ahora usa el argumento java_callback el usuario que se pasa para almacenar toda la información que necesita en lugar de las búsquedas globales, y tiene que pasar / recibir los argumentos de la función a través de la llamada ffi. Nuestro mapa de tipos Callback_t ahora hace uso de la función adicional que agregamos a través de %extend para ayudar a configurar el puntero de función para el cierre que realmente necesitamos.

Una cosa importante a tener en cuenta aquí es que usted es responsable por el lado de Java de administrar el ciclo de vida de las instancias de devolución de llamada, no hay forma de hacer que esa información sea visible desde el lado C, por lo que la recolección prematura de basura es un riesgo.

Para compilar y ejecutar esto, el trabajo implements necesita extends en run.java y el compilador necesita tener -lffi agregado. Aparte de eso, funciona como antes.

Dado que en su caso el lenguaje que se envuelve es C ++ no C, podemos simplificar un poco el código JNI un poco confiando en la función de directores de SWIG para ayudarnos un poco. Esto luego se convierte en:

%module(directors="1") test %{ #include "test.h" #include <assert.h> #include <ffi.h> %} %feature("director") Callback; // This rename makes getting the C++ generation right slightly simpler %rename(Log) Callback::call; // Make it abstract %javamethodmodifiers Callback::call "public abstract" %typemap(javaout) void Callback::call ";" %typemap(javaclassmodifiers) Callback "public abstract class" %typemap(jstype) Callback_t "Callback"; %typemap(jtype) Callback_t "long"; %typemap(jni) Callback_t "jlong"; %typemap(javain) Callback_t "$javainput.prepare_fp()"; %typemap(in) Callback_t { $1 = (Callback_t)$input; } %inline %{ struct Callback { virtual void call(Log l, const char *s) = 0; virtual ~Callback() { if (bound_fn) ffi_closure_free(closure); } jlong prepare_fp() { if (!bound_fn) { int ret; args[0] = &ffi_type_uint; args[1] = &ffi_type_pointer; closure = static_cast<decltype(closure)>(ffi_closure_alloc(sizeof(ffi_closure), &bound_fn)); assert(closure); ret=ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 2, &ffi_type_void, args); assert(ret == FFI_OK); ret=ffi_prep_closure_loc(closure, &cif, java_callback, this, bound_fn); assert(ret == FFI_OK); } return *((jlong*)&bound_fn); } private: ffi_closure *closure; ffi_cif cif; ffi_type *args[2]; void *bound_fn; static void java_callback(ffi_cif *cif, void *ret, void *args[], void *userdata) { (void)cif; (void)ret; Callback *cb = static_cast<Callback*>(userdata); printf("Starting arg parse/n"); Log l = (Log)*(unsigned*)args[0]; const char *s = *(const char**)args[1]; printf("In java_callback: %s/n", s); cb->call(l, s); } }; %} %include "test.h"

Este archivo .i, que ha simplificado enormemente el código requerido dentro de java_callback ahora es un reemplazo java_callback para la implementación previa de libffi y C. Casi todos los cambios están relacionados con permitir a los directores de forma sensata y corregir algunos C-ismos. Todo lo que tenemos que hacer ahora es llamar al método C ++ virtual puro desde nuestra devolución de llamada y SWIG ha generado un código que maneja el resto.