Pasar una matriz a una función envolvente como puntero + tamaño o rango

Dado un encabezado como:

#include  #include  #include  inline void foo(const signed char *arr, size_t sz) { std::copy_n(arr, sz, std::ostream_iterator(std::cout, "\n")); } inline void bar(const signed char *begin, const signed char *end) { std::copy(begin, end, std::ostream_iterator(std::cout, "\n")); } 

(Utilicé C ++ 11 aquí por conveniencia, esto podría ser C o C ++ si cambiaras las implementaciones)

¿Cómo puedo ajustar estas funciones para tomar solo una matriz en el lado de Java y usar el tamaño (conocido) de la matriz para proporcionar el segundo parámetro para estas funciones?

El quid de esto es que para envolver cualquiera de estas funciones querrá usar un mapa de tipos de múltiples argumentos .

El preámbulo es bastante estándar para SWIG. Utilicé mi prgama favorito personal para cargar automáticamente la biblioteca compartida sin que el usuario de la interfaz tenga que saber:

 %module test %{ #include "test.hh" %} %pragma(java) jniclasscode=%{ static { try { System.loadLibrary("test"); } catch (UnsatisfiedLinkError e) { System.err.println("Native code library failed to load. \n" + e); System.exit(1); } } %} 

Primero, necesitará usar algunos mapas de tipos Java para indicar a SWIG que use byte[] como el tipo de ambas partes de la interfaz Java: el JNI y el contenedor que lo llama. En el archivo de módulo de generación jbyteArray tipo JNI jbyteArray . Estamos pasando la entrada directamente desde la interfaz SWIG al JNI que genera.

 %typemap(jtype) (const signed char *arr, size_t sz) "byte[]" %typemap(jstype) (const signed char *arr, size_t sz) "byte[]" %typemap(jni) (const signed char *arr, size_t sz) "jbyteArray" %typemap(javain) (const signed char *arr, size_t sz) "$javainput" 

Cuando esto se hace, podemos escribir un mapa de tipos de múltiples argumentos:

 %typemap(in,numinputs=1) (const signed char *arr, size_t sz) { $1 = JCALL2(GetByteArrayElements, jenv, $input, NULL); $2 = JCALL1(GetArrayLength, jenv, $input); } 

El trabajo de en el mapa de tipos es convertir lo que la llamada JNI nos da a lo que realmente espera la función real como entrada. numinputs=1 para indicar que los dos argumentos de función real solo toman una entrada en el lado de Java, pero este es el valor predeterminado de todos modos, por lo que no es necesario indicarlo explícitamente.

En este tipo de mapa $1 es el primer argumento del mapa de tipos, es decir, el primer argumento de nuestra función en este caso. Lo configuramos pidiendo un puntero al almacenamiento subyacente de la matriz de Java (que puede o no ser una copia realmente). Configuramos $2 , el segundo argumento de tipo de mapa es el tamaño de la matriz.

Las macros JCALLn aquí se aseguran de que el mapa de tipos pueda comstackrse con C y C ++ JNI. Se expande a la convocatoria apropiada para el idioma.

Necesitamos otro mapa de tipos para limpiar una vez que la llamada a la función real haya regresado:

 %typemap(freearg) (const signed char *arr, size_t sz) { // Or use 0 instead of ABORT to keep changes if it was a copy JCALL3(ReleaseByteArrayElements, jenv, $input, $1, JNI_ABORT); } 

Esto llama a ReleaseByteArrayElements para decirle a la JVM que hemos terminado con la matriz. Necesita el puntero y el objeto de matriz de Java del que lo obtuvimos. Además, toma un parámetro que indica si los contenidos deben copiarse si fueron modificados y el puntero que obtuvimos fue una copia en primer lugar. (El argumento que pasamos NULL es un puntero opcional a un jboolean que indica si se nos ha dado una copia).

Para la segunda variante, los mapas de tipos son sustancialmente similares:

 %typemap(in,numinputs=1) (const signed char *begin, const signed char *end) { $1 = JCALL2(GetByteArrayElements, jenv, $input, NULL); const size_t sz = JCALL1(GetArrayLength, jenv, $input); $2 = $1 + sz; } %typemap(freearg) (const signed char *begin, const signed char *end) { // Or use 0 instead of ABORT to keep changes if it was a copy JCALL3(ReleaseByteArrayElements, jenv, $input, $1, JNI_ABORT); } %typemap(jtype) (const signed char *begin, const signed char *end) "byte[]" %typemap(jstype) (const signed char *begin, const signed char *end) "byte[]" %typemap(jni) (const signed char *begin, const signed char *end) "jbyteArray" %typemap(javain) (const signed char *begin, const signed char *end) "$javainput" 

La única diferencia es el uso de la variable local sz para calcular el armado end utilizando el puntero de begin .

Lo único que queda por hacer es decirle a SWIG que envuelva el archivo de encabezado, utilizando los mapas de tipos que acabamos de escribir:

 %include "test.hh" 

Probé ambas funciones con:

 public class run { public static void main(String[] argv) { byte[] arr = {0,1,2,3,4,5,6,7}; System.out.println("Foo:"); test.foo(arr); System.out.println("Bar:"); test.bar(arr); } } 

Lo cual funcionó como se esperaba.

Para mayor comodidad, he compartido los archivos que utilicé al escribir esto en mi sitio . Cada línea de cada archivo en ese archivo se puede reconstruir siguiendo esta respuesta secuencialmente.


Como referencia, podríamos haberlo hecho sin llamadas JNI, usando %pragma(java) modulecode para generar una sobrecarga que usamos para convertir la entrada (en Java puro) a la forma esperada por las funciones reales. Para eso, el archivo del módulo habría sido:

 %module test %{ #include "test.hh" %} %include  %array_class(signed char, ByteArray); %pragma(java) modulecode = %{ // Overload foo to take an array and do a copy for us: public static void foo(byte[] array) { ByteArray temp = new ByteArray(array.length); for (int i = 0; i < array.length; ++i) { temp.setitem(i, array[i]); } foo(temp.cast(), array.length); // if foo can modify the input array we'll need to copy back to: for (int i = 0; i < array.length; ++i) { array[i] = temp.getitem(i); } } // How do we even get a SWIGTYPE_p_signed_char for end for bar? public static void bar(byte[] array) { ByteArray temp = new ByteArray(array.length); for (int i = 0; i < array.length; ++i) { temp.setitem(i, array[i]); } bar(temp.cast(), make_end_ptr(temp.cast(), array.length)); // if bar can modify the input array we'll need to copy back to: for (int i = 0; i < array.length; ++i) { array[i] = temp.getitem(i); } } %} // Private helper to make the 'end' pointer that bar expects %javamethodmodifiers make_end_ptr "private"; %inline { signed char *make_end_ptr(signed char *begin, int sz) { return begin+sz; } } %include "test.hh" %pragma(java) jniclasscode=%{ static { try { System.loadLibrary("test"); } catch (UnsatisfiedLinkError e) { System.err.println("Native code library failed to load. \n" + e); System.exit(1); } } %} 

Además de las obvias (dos) copias requeridas para obtener los datos en el tipo correcto (no hay una manera trivial de pasar de byte[] a SWIGTYPE_p_signed_char ) y esto tiene otra desventaja: es específico para las funciones foo y bar , mientras que los mapas de tipos que escribió antes no son específicos para una función dada: se aplicarán en cualquier lugar que coincidan, incluso varias veces en la misma función si tiene una función que toma dos rangos o dos combinaciones de puntero y longitud. La única ventaja de hacerlo de esta manera es que si tiene otras funciones SWIGTYPE_p_signed_char le dan SWIGTYPE_p_signed_char ese momento, todavía tendrá las sobrecargas disponibles para usar si lo desea. Incluso en el caso en que tenga un ByteArray del %array_class no podrá hacer la aritmética del puntero en Java necesaria para generar el end para usted.

La forma original mostrada brinda una interfaz más limpia en Java, con las ventajas adicionales de no hacer copias excesivas y ser más reutilizable.


Otro enfoque alternativo para envolver sería escribir algunas %inline sobrecargas en %inline para foo y bar :

 %inline { void foo(jbyteArray arr) { // take arr and call JNI to convert for foo } void bar(jbyteArray arr) { // ditto for bar } } 

Estos se presentan como sobrecargas en la interfaz de Java, pero todavía son específicos del módulo y, además, el JNI requerido aquí es más complejo de lo que de otro modo debería ser: debe organizarse para obtener jenv alguna manera, que no es accesible por defecto. Las opciones son una llamada lenta para obtenerlo, o un numinputs=0 que llena automáticamente el parámetro. De cualquier manera, el mapa de tipos de múltiples argumentos parece mucho mejor.