Hace un par de días vi un tweet de Brian Goetz, pero solo hoy mis manos llegaron a jugar con ejemplos.

Quiero hablar brevemente sobre esto.
Sobre
el proyecto de Panamá sobre Habré
ya escribió . Para entender qué es y por qué, debe leer la entrevista aquí. Solo mostraré un par de ejemplos simples de cómo usar el
aglutinante nativo .
En primer lugar, necesita el compilador de C. Si está utilizando Linux o MacOS, entonces ya tiene uno. Si es Windows, primero deberá instalar
Build Tools para Visual Studio 2017 . Y, por supuesto, necesita OpenJDK con soporte de Panamá. Puede obtenerlo compilando la rama "extranjera" del
repositorio correspondiente o descargando la
compilación Early-Access .
Comencemos con un ejemplo mínimo: una función simple que agrega dos números:
adder.h#ifndef _ADDER_H #define _ADDER_H __declspec(dllexport) int add(int, int); #endif
adder.c #include "adder.h" __declspec(dllexport) int add(int a, int b) { return a + b; }
Compilar en una DLL
cl /LD adder.c
Y usar en código java
import java.foreign.Library; import java.foreign.Libraries; import java.foreign.annotations.NativeHeader; import java.foreign.annotations.NativeFunction; import java.lang.invoke.MethodHandles; public class App { @NativeHeader interface Adder { @NativeFunction("(i32 i32)i32") int add(int a, int b); } public static void main(String[] args) { Library lib = Libraries.loadLibrary(MethodHandles.lookup(), "adder"); Adder adder = Libraries.bind(Adder.class, lib); System.out.println(adder.add(3, 5)); } }
La fuente debe tener muchas familiaridades para aquellos que usaron JNR: se declara la interfaz de la biblioteca nativa, se carga la biblioteca, se asocia con la interfaz y se llama a la función nativa. La principal diferencia es el uso del
lenguaje de definición de
diseño en las anotaciones para describir la asignación de datos nativos a los tipos de Java.
Es fácil adivinar que la expresión "
(i32 i32)i32
" denota una función que toma dos enteros de 32 bits y devuelve un entero de 32 bits. La etiqueta
i
denota uno de los tres tipos básicos: un entero con orden de bytes little endian. Además, a menudo se encuentran
u
y
f
: un número entero sin signo y un número de coma flotante, respectivamente. Se usan las mismas etiquetas para indicar el orden de big-endian, pero en mayúsculas:
I
,
U
,
F
Otra etiqueta común es
v
, utilizada para
void
. El número que sigue a la etiqueta indica el número de bits utilizados.
Si el número está antes de la etiqueta, entonces la etiqueta denota una matriz:
[42f32]
- una matriz de 42 elementos de tipo
float
. Etiquetas de grupo de corchetes. Además de las matrices, esto puede usarse para indicar estructuras (
[i32 i32]
- una estructura con dos campos de tipo
int
) y uniones (
[u64|u64:f32]
-
long
o un puntero para
float
).
Se usan dos puntos para indicar punteros. Por ejemplo,
u64:i32
es un puntero a
int
,
u64:v
es un puntero de tipo
void
y
u64:[i32 i32]
es un puntero a una estructura.
Armado con esta información, complicaremos un poco el ejemplo.
totalizer.c __declspec(dllexport) long sum(int vals[], int size) { long r = 0; for (int i = 0; i < size; i++) { r += vals[i]; } return r; }
App.java import java.foreign.Library; import java.foreign.Libraries; import java.foreign.NativeTypes; import java.foreign.Scope; import java.foreign.annotations.NativeHeader; import java.foreign.annotations.NativeFunction; import java.foreign.memory.Array; import java.foreign.memory.Pointer; import java.lang.invoke.MethodHandles; public class App { @NativeHeader interface Totalizer { @NativeFunction("(u64:i32 i32)u64") long sum(Pointer<Integer> vals, int size); } public static void main(String[] args) { Library lib = Libraries.loadLibrary(MethodHandles.lookup(), "totalizer"); Totalizer totalizer = Libraries.bind(Totalizer.class, lib); try (Scope scope = Scope.newNativeScope()) { Array<Integer> array = scope.allocateArray(NativeTypes.INT, new int[] { 23, 15, 4, 16, 42, 8 }); System.out.println(totalizer.sum(array.elementPointer(), 3)); } } }
Varios elementos nuevos aparecieron en el código de Java a la vez:
Scope
,
Array
y
Pointer
. Cuando trabaje con código nativo, tendrá que operar datos fuera del montón, lo que significa que tendrá que asignar memoria de forma independiente, liberar de forma independiente y controlar la relevancia de los punteros. Afortunadamente,
Scope
se ocupa de todas estas preocupaciones. Sus métodos hacen que sea fácil y conveniente asignar memoria no especificada, memoria para matrices, estructuras y líneas C, obtener punteros a esta memoria y también liberarla automáticamente después de completar el bloque de prueba con recursos y cambiar el estado de los punteros creados para que la llamada condujo a la excepción, no a la caída de la máquina virtual.
Para ver el trabajo de las estructuras y los punteros, vamos a complicar un poco más el ejemplo.
mover.h #ifndef _ADDER_H #define _ADDER_H typedef struct { int x; int y; } Point; __declspec(dllexport) void move(Point*); #endif
mover.c #include "mover.h" __declspec(dllexport) void move(Point *point) { point->x = 4; point->y = 2; }
App.java import java.foreign.Library; import java.foreign.Libraries; import java.foreign.Scope; import java.foreign.annotations.NativeHeader; import java.foreign.annotations.NativeFunction; import java.foreign.annotations.NativeStruct; import java.foreign.annotations.NativeGetter; import java.foreign.memory.LayoutType; import java.foreign.memory.Pointer; import java.foreign.memory.Struct; import java.lang.invoke.MethodHandles; public class App { @NativeStruct("[i32(x) i32(y)](Point)") interface Point extends Struct<Point> { @NativeGetter("x") int x(); @NativeGetter("y") int y(); } @NativeHeader interface Mover { @NativeFunction("(u64:[i32 i32])v") void move(Pointer<Point> point); } public static void main(String[] args) { Library lib = Libraries.loadLibrary(MethodHandles.lookup(), "mover"); Mover mover = Libraries.bind(Mover.class, lib); try (Scope scope = Scope.newNativeScope()) { Pointer<Point> point = scope.allocate( LayoutType.ofStruct(Point.class)); mover.move(point); System.out.printf("X: %d Y: %d%n", point.get().x(), point.get().y()); } } }
Aquí es interesante cómo se declara la interfaz de la estructura y cómo se le asigna memoria. Tenga en cuenta que ha aparecido un nuevo elemento en la declaración ldl: los valores entre paréntesis después de las etiquetas. Esta es una anotación de etiqueta en forma abreviada. El formulario completo se vería así:
i32(name=x)
. La anotación de etiqueta le permite correlacionarla con el método de interfaz.
Antes de pasar a la promesa en el título, queda por resaltar otra forma de interactuar con el código nativo. Todos los ejemplos anteriores se denominan funciones nativas, pero a veces el código nativo necesita llamar al código java. Por ejemplo, si queremos ordenar una matriz usando
qsort
, necesitamos una devolución de llamada.
import java.foreign.Library; import java.foreign.Libraries; import java.foreign.NativeTypes; import java.foreign.Scope; import java.foreign.annotations.NativeHeader; import java.foreign.annotations.NativeFunction; import java.foreign.annotations.NativeCallback; import java.foreign.memory.Array; import java.foreign.memory.Callback; import java.foreign.memory.Pointer; import java.lang.invoke.MethodHandles; public class App { @NativeHeader interface StdLib { @NativeFunction("(u64:[0i32] i32 i32 u64:(u64:i32 u64:i32) i32)v") void qsort(Pointer<Integer> base, int nitems, int size, Callback<QComparator> comparator); @NativeCallback("(u64:i32 u64:i32)i32") interface QComparator { int compare(Pointer<Integer> p1, Pointer<Integer> p2); } } public static void main(String[] args) { Library lib = Libraries.loadLibrary(MethodHandles.lookup(), "msvcr120"); StdLib stdLib = Libraries.bind(StdLib.class, lib); try (Scope scope = Scope.newNativeScope()) { Array<Integer> array = scope.allocateArray(NativeTypes.INT, new int[] { 23, 15, 4, 16, 42, 8 }); Callback<StdLib.QComparator> cb = scope.allocateCallback( (p1, p2) -> p1.get() - p2.get()); stdLib.qsort(array.elementPointer(), (int) array.length(), Integer.BYTES, cb); for (int i = 0; i < array.length(); i++) { System.out.printf("%d ", array.get(i)); } System.out.println(); } } }
Es fácil ver que los anuncios ldl, que no son particularmente fáciles de leer, se convierten rápidamente en diseños furiosos. Pero
qsort
no es la función más difícil. Además, puede haber docenas de estructuras y docenas de funciones en bibliotecas reales, escribir interfaces para ellos es una tarea ingrata. Afortunadamente, ambos problemas se resuelven fácilmente utilizando la utilidad
jextract
, que generará todas las interfaces necesarias basadas en los archivos de encabezado. Volvamos al primer ejemplo y procesémoslo con esta utilidad.
jextract -L . -l adder -o adder.jar -t "com.example" adder.h
Y use el archivo jar resultante para compilar y ejecutar código java:
javac -cp adder.jar Example.java java -cp .;adder.jar Example
Si bien no es particularmente impresionante, pero le permite comprender el principio. Y ahora hagamos lo mismo con python37.dll (¡finalmente!)
import java.foreign.Scope; import java.foreign.memory.Pointer; import static org.python.Python_h.*; import static org.python.pylifecycle_h.*; import static org.python.pythonrun_h.*; public class App { public static void main(String[] args) { Py_Initialize(); try (Scope s = Scope.newNativeScope()) { PyRun_SimpleStringFlags( s.allocateCString("print(sum([23, 15, 4, 16, 42, 8]))\n"), Pointer.nullPointer()); } Py_Finalize(); } }
Generamos interfaces:
jextract -L "C:\Python37" -l python37 -o python.jar -t "org.python" --record-library-path C:\Python37\include\Python.h
Compilar y ejecutar:
javac -cp python.jar App.java java -cp .;python.jar App
¡Felicitaciones, su aplicación Java acaba de descargar el intérprete de Python y ejecutó el script en él!
Se pueden encontrar más ejemplos en las
instrucciones para pioneros .
Los proyectos de Maven con ejemplos del artículo se pueden encontrar
en GitHub .
PS API actualmente está experimentando cambios rápidos. En las presentaciones de hace un par de meses, es fácil ver el código que no se compilará. Los ejemplos de este artículo no son inmunes a esto. Si te encuentras con esto, envíame un mensaje, intentaré solucionarlo.