¿Cómo convierto al tipo SWIGTYPE_p_void en enlaces Java generados por SWIG?

Estoy desarrollando algunos enlaces Java generados por SWIG para una biblioteca C. La biblioteca contiene funciones que toman parámetros de tipo void *. En el lado C, estos normalmente se pasarían como un puntero a una matriz de tipo float or int emitir a void *. En los enlaces Java generados, esto da como resultado métodos que toman parámetros de tipo SWIGTYPE_p_void.

¿Cuál es la mejor manera de construir una matriz de flotantes/ints en los enlaces de Java para que puedan pasarse como tipo? SWIGTYPE_p_void a estos métodos?

En este momento estoy definiendo una función auxiliar en mi archivo example.i:

void *floata_to_voidp(float f[])
{
    return (void *)f;
}

Y luego en el lado de Java haciendo algo como esto:

float foo[] = new float[2];
SWIGTYPE_p_void av = null;

// do something with foo

av = example.floata_to_voidp(foo);
example.myfunction(av);

Esto parece bastante feo, especialmente porque también necesitaría un inta_to_voidp() etc. en mi archivo de interfaz SWIG para cada tipo de conversión que quiero admitir.

¿Hay alguna manera de hacer esto sin funciones auxiliares y con menos código adicional en el lado de Java para convertir tipos de datos?

ACTUALIZACIÓN (17 / 6 / 12): para dar detalles adicionales a la pregunta: lo que estoy tratando de hacer es tomar un conjunto de funciones C, con prototipo int foo(const float *data, int N, const void *argv, float *result) y asignarlos a métodos en el lado de Java donde se puede pasar una matriz de tipo arbitrario como argv. Tenga en cuenta que argv is const void * y no void *.

preguntado el 12 de junio de 12 a las 22:06

2 Respuestas

Hay una alternativa a esta respuesta, es muy diferente y da una solución más natural a este problema, más cercana a lo que estabas buscando originalmente. Las otras sugerencias se centraron en agregar sobrecargas (tediosas, manuales) o hacer que la array_classes implementar una interfaz común de una forma u otra.

Lo que pasa por alto es que Object es un buen partido para void* en Java la mayor parte del tiempo. Incluso las matrices en Java son Objects. Esto significa que si tiene un mapa SWIG void* a Object aceptará como entradas cualquier matriz que desee pasar. Con un poco de cuidado y algo de JNI, podemos obtener un puntero al inicio de esa matriz para pasar a la función. Obviamente necesitamos rechazar la no matriz. Objects con una excepción sin embargo.

Todavía terminamos escribiendo algunas funciones de ayuda (privadas) para organizar la extracción del puntero subyacente real y liberarlo cuando haya terminado, pero lo bueno de esta solución es que solo tenemos que hacer esto una vez y luego terminamos con un mapa de tipos que se puede usar para cualquier función que tome una matriz como void* Me gusta esto.

Terminé con la siguiente interfaz SWIG para esta solución:

%module test

%{
#include <stdint.h>

void foo(void *in) {
  printf("%p, %d, %g\n", in, *(jint*)in, *(jdouble*)in);
}
%}

%typemap(in,numinputs=0) JNIEnv *env "$1 = jenv;"

%javamethodmodifiers arr2voidd "private";
%javamethodmodifiers arr2voidi "private";
%javamethodmodifiers freearrd "private";
%javamethodmodifiers freearri "private";
%inline %{
jlong arr2voidd(JNIEnv *env, jdoubleArray arr) {
  void *ptr = (*env)->GetDoubleArrayElements(env, arr, NULL);
  return (intptr_t)ptr;
}

void freearrd(JNIEnv *env, jdoubleArray arr, jlong map) {
  void *ptr = 0;
  ptr = *(void **)&map;
  (*env)->ReleaseDoubleArrayElements(env, arr, ptr, JNI_ABORT);
}

jlong arr2voidi(JNIEnv *env, jintArray arr) {
  void *ptr = (*env)->GetIntArrayElements(env, arr, NULL);
  return (intptr_t)ptr;
}

void freearri(JNIEnv *env, jintArray arr, jlong map) {
  void *ptr = 0;
  ptr = *(void **)&map;
  (*env)->ReleaseIntArrayElements(env, arr, ptr, JNI_ABORT);
}
%}


%pragma(java) modulecode=%{
  private static long arrPtr(Object o) {
    if (o instanceof double[]) {
      return arr2voidd((double[])o);
    }
    else if (o instanceof int[]) {
      return arr2voidi((int[])o);
    }
    throw new IllegalArgumentException();
  }

  private static void freeArrPtr(Object o, long addr) {
    if (o instanceof double[]) {
      freearrd((double[])o, addr);
      return;
    }
    else if (o instanceof int[]) {
      freearri((int[])o, addr);
      return;
    }
    throw new IllegalArgumentException();
  }
%}

%typemap(jstype) void *arr "Object"
%typemap(javain,pre="    long tmp$javainput = arrPtr($javainput);",post="      freeArrPtr($javainput, tmp$javainput);") void *arr "tmp$javainput"

void foo(void *arr);

Esto lo implementa para dos tipos de matrices, hay un número finito pequeño y también podría usar fragmentos o macros para ayudar con esto. Internamente, SWIG utiliza un jlong para representar punteros. Entonces, para cada tipo de matriz, necesitamos una función que devuelva un puntero para una matriz dada y otro para liberarlo. Estos son privados y forman parte de la clase del módulo: nadie más que el módulo necesita saber cómo funciona esto.

Hay entonces dos funciones que toman la Object y use instanceof (feo, pero las matrices en Java no tienen ninguna otra base o interfaz común y los genéricos no ayudan) y llame a la función correcta para obtener/liberar los punteros.

Con estos, solo son dos mapas de tipos para configurar SWIG para usarlo para todos void *arr argumentos El mapa de tipos jstype le indica a SWIG que use Object para void* en estos casos. El mapa de tipos de Java organiza una variable local temporal para mantener el puntero (en un long) y luego para que se use para realizar la llamada y se limpie una vez que la llamada haya tenido éxito o haya fallado.

contestado el 23 de mayo de 17 a las 13:05

De hecho, esta es una técnica muy buena, y su código de prueba funciona perfectamente. Eso casi funciona para mí en contexto excepto por el detalle que mis funciones C realmente toman const void * y no void *. Puedo evitar escribir funciones de envoltura con %apply void *in {const void *argv};, pero a menos que vuelva a declarar todas mis funciones (¡hay 50!), tomando void *, Yo obtengo: No se puede aplicar (anulado *in). No se definen mapas de tipo. ¿Hay alguna manera de que pueda decir simplemente "tratar const void * as void *"? - jb

@JamieBullock Creo que probablemente estés usando %apply en el lugar equivocado necesitas usarlo después de todos los typemaps han sido definidos. así que agregué %apply void *arr { const void *argv } al final de la interfaz y funcionó bien. También debe tener cuidado de hacer coincidir exactamente los nombres de los parámetros si está haciendo coincidir eso; mi ejemplo los llamó arr pero el %apply mostraste lo llamas in, aunque no estoy seguro de si eso es porque lo renombraste todo. - Flexografía

eso no me funciona pero tienes razon %apply void *in {const void *argv}; fue erróneo. al final, decidí hacer un script de 1 línea en mi script de compilación para generar un redeclare.i con todas mis funciones redeclaradas como tomando void * en lugar de const void *, y luego agregó %include redeclare.i hasta el final de mi principal .i Archivo. Tal vez no sea tan elegante, pero funciona. Esto, combinado con su solución anterior, ahora me permite pasar libremente las matrices de Java como void *, lo cual es genial. ¡Gracias! - jb

@JamieBullock tal vez debería haber sido más explícito: el %apply in esta versión modificada de mi respuesta generó la interfaz Java correcta. - Flexografía

La solución más simple es usar SWIG <carrays.i> para hacer un tipo que envuelva una matriz of float y una variedad de int y cualquier otro tipo que le interese. Estos se pueden convertir en SWIGTYPE_p_float etc. trivialmente usando el cast() función miembro. El problema es que esto no se puede convertir automáticamente en un SWIGTYPE_p_void desde dentro de Java. En teoría, podrías llamar:

new SWIGTYPE_p_void(FloatArray.getCPtr(myfloatarr));

pero por varias razones (no menos importante es engorroso) eso es menos que ideal. (También tiene problemas con la propiedad de la memoria y la recolección de basura).

Entonces, en cambio, definí una interfaz:

public interface VoidPtr {
  public long asVoidPtr();
}

Que podemos hacer que la versión envuelta de su biblioteca tome como entrada y las clases de matriz FloatArray, IntArray etc implementar para nosotros.

Esto termina con el archivo del módulo:

%module test

%include <carrays.i>

%typemap(javainterfaces) FloatArray "VoidPtr"
%typemap(javainterfaces) IntArray "VoidPtr"

%typemap(javacode) FloatArray %{
  public long asVoidPtr() {
    return getCPtr(this);    
  }
%}

%typemap(javacode) IntArray %{
  public long asVoidPtr() {
    return getCPtr(this);
  }
%}

%array_class(float, FloatArray);
%array_class(int, IntArray);

%typemap(jstype) void *arr "VoidPtr"
%typemap(javain) void *arr "$javainput.asVoidPtr()"

void foo(void *arr);

que modifica void *arr ser tratado como nuestro VoidPtr escribe y automáticamente llama al asVoidPtr() método. podrías usar copia de mapas de tipos o macros para hacer esto menos repetitivo. (Nota, hay un posible problema con recolección de basura prematura eso podría necesitar ser abordado aquí dependiendo de cómo planeó usar esto)

Esto nos permite escribir código como:

public class run {
  public static void main(String[] argv) {
    FloatArray arr = new FloatArray(100);
    test.foo(arr);    
  }
}

Creo que esta es la solución más fácil y limpia. Sin embargo, hay varias otras formas de resolver esto:

  1. También es posible escribir algún código que tome una matriz de Java real en lugar de solo el SWIG array_class e implemente esta interfaz llamando a una función JNI para obtener el puntero subyacente. Sin embargo, tendría que escribir una versión de esto para cada tipo primitivo, al igual que el anterior.

    Un archivo de interfaz podría verse así:

    %module test
    
    %{
    void foo(void *arr);
    %}
    
    %typemap(in,numinputs=0) JNIEnv *env "$1 = jenv;"
    
    %rename(foo) fooFloat;
    %rename(foo) fooInt;
    %inline %{
    void fooFloat(JNIEnv *env, jfloatArray arr) {
      jboolean isCopy;
      foo((*env)->GetFloatArrayElements(env, arr, &isCopy));
      // Release after call with desired semantics
    }
    
    void fooInt(JNIEnv *env, jintArray arr) {
      jboolean isCopy;
      foo((*env)->GetIntArrayElements(env, arr, &isCopy));
      // Release after call
    }
    %}
    
    void foo(void *arr);
    

    Lo que luego te da sobrecargas de foo que toma float[] y int[] al igual que SWIGTYPE_p_void.

  2. Podrías usar un truco con una unión:

    %inline %{
      union Bodge {
        void *v;
        float *f;
        int *i;
      };
    %}
    

    aunque esto se considera una mala forma, genera una interfaz Java que se puede usar para convertir de SWIGTYPE_p_int a SWIGTYPE_p_void.

  3. Creo que es posible hacer FloatArray heredar de SWIGTYPE_p_void, algo como el siguiente código compilado pero no probado:

    %module test
    
    %include <carrays.i>
    
    %typemap(javabase) FloatArray "SWIGTYPE_p_void"
    %typemap(javabody) FloatArray %{
      private long swigCPtr; // Minor bodge to work around private variable in parent
      private boolean swigCMemOwn;
      public $javaclassname(long cPtr, boolean cMemoryOwn) {
        super(cPtr, cMemoryOwn);
        this.swigCPtr = SWIGTYPE_p_void.getCPtr(this);
        swigCMemOwn = cMemoryOwn;
      }
    %}
    
    %array_class(float, FloatArray);
    
    void foo(void *arr);
    

    Esto duplica el puntero en el lado de Java, pero nada cambia eso (actualmente) ni en el puntero vacío ni en las clases de matriz, por lo que no es un problema tan grande como parece. (También podría protegerlo en la clase base con un mapa de tipos alternativo, creo, o usar una versión modificada de carrays.i eso consigue swigCPtr mediante el getCPtr función en su lugar)

contestado el 23 de mayo de 17 a las 13:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.