Пару дней назад увидел твит Брайана Гетца, но только сегодня дошли руки поиграться с примерами.

Про это и хочу кратко рассказать.
О проекте Panama на Хабре уже писали. Чтобы понять, что это и зачем, стоит прочитать интервью по ссылке. Я же просто покажу пару простых примеров того, как можно применить native binder.
Прежде всего, вам понадобится компилятор C. Если вы используете Linux или MacOS, то он у вас уже есть. Если Windows, то придётся сначала установить Build Tools for Visual Studio 2017. И, конечно же, вам потребуется OpenJDK с поддержкой «Панамы». Получить его можно либо сборкой ветки «foreign» соответствующего репозитория, либо загрузкой Early-Access билда.
Начнём с минимального примера — простой функции, складывающей два числа:
adder.h
adder.c
Компилируем в DLL
И используем в java-коде
В исходнике должно быть много знакомого для тех, кто использовал JNR: объявляется интерфейс нативной библиотеки, библиотека загружается, связывается с интерфейсом и происходит вызов нативной функции. Основное отличие — использование Layout Definition Language в аннотациях для описания схемы отображения нативных данных на типы Java.
Несложно догадаться, что выражение "
Если число стоит перед меткой, то метка обозначает массив:
Для обозначения указателей используется двоеточие. Например,
Вооружившись этой информацией, немного усложним пример.
totalizer.c
App.java
В java-коде появилось сразу несколько новых элементов —
Чтобы посмотреть в работе структуры и указатели, усложним пример ещё немного.
mover.h
mover.c
App.java
Интерес здесь представляет то, как объявляется интерфейс структуры и как под неё выделяется память. Обратите внимание, что в ldl-объявлении появился новый элемент — значения в круглых скобках после меток. Это аннотация метки в сокращённой форме. Полная форма выглядела бы так:
Прежде, чем переходить к обещанному в заголовке, осталось осветить ещё один способ взаимодействия с нативным кодом. Все предыдущие примеры вызывали нативные функции, но иногда нативному коду требуется вызывать java-код. Например, если мы хотим отсортировать массив с помощью
Легко заметить, что ldl-объявления, и так не особо простые для восприятия, быстро превращаются в зубодробительные конструкции. А ведь
И используем полученный jar-файл для сборки и запуска java-кода:
Пока не особенно впечатляет, но позволяет понять принцип. А теперь проделаем то же самое с python37.dll (наконец-то!)
Генерируем интерфейсы:
Компилируем и запускаем:
Поздравляю, ваше java-приложение только что загрузило в себя интерпретатор Python и выполнило в нём скрипт!

Больше примеров можно посмотреть в инструкции для первопроходцев.
Maven-проекты с примерами из статьи можно найти на GitHub.
P.S. API сейчас находится в стадии бурных изменений. В презентациях вышедших пару месяцев назад докладов легко увидеть код, который не будет компилироваться. Не застрахованы от этого и примеры из этой статьи. Если вы столкнётесь с этим, отправьте мне сообщение, постараюсь исправить.

Про это и хочу кратко рассказать.
О проекте Panama на Хабре уже писали. Чтобы понять, что это и зачем, стоит прочитать интервью по ссылке. Я же просто покажу пару простых примеров того, как можно применить native binder.
Прежде всего, вам понадобится компилятор C. Если вы используете Linux или MacOS, то он у вас уже есть. Если Windows, то придётся сначала установить Build Tools for Visual Studio 2017. И, конечно же, вам потребуется OpenJDK с поддержкой «Панамы». Получить его можно либо сборкой ветки «foreign» соответствующего репозитория, либо загрузкой Early-Access билда.
Начнём с минимального примера — простой функции, складывающей два числа:
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; }
Компилируем в DLL
cl /LD adder.c
И используем в 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)); } }
В исходнике должно быть много знакомого для тех, кто использовал JNR: объявляется интерфейс нативной библиотеки, библиотека загружается, связывается с интерфейсом и происходит вызов нативной функции. Основное отличие — использование Layout Definition Language в аннотациях для описания схемы отображения нативных данных на типы Java.
Несложно догадаться, что выражение "
(i32 i32)i32" обозначает функцию принимающую два целых 32-битных числа и возвращающую целое 32-битное число. Метка i обозначает один из трёх основных типов — целое число с порядком байт little-endian. Кроме него часто встречаются u и f — беззнаковое целое и число с плавающей точкой, соответственно. Для обозначения порядка big-endian используются те же метки, но в верхнем регистре — I, U, F. Ещё одна часто встречающаяся метка — это v, используемая для void. Число идущее следом за меткой обозначают количество используемых бит.Если число стоит перед меткой, то метка обозначает массив:
[42f32] — массив из 42 элементов типа float. Квадратные скобки группируют метки. Кроме массивов это может использоваться для обозначения структур ([i32 i32] — структура с двумя полями типа int) и объединений ([u64|u64:f32] — long или указатель на float).Для обозначения указателей используется двоеточие. Например,
u64:i32 — указатель на int, u64:v — указатель типа void, а u64:[i32 i32] — указатель на структуру.Вооружившись этой информацией, немного усложним пример.
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)); } } }
В java-коде появилось сразу несколько новых элементов —
Scope, Array и Pointer. Работая с нативным кодом, вам придётся оперировать off-heap данными, а значит придётся самостоятельно выделять память, самостоятельно освобождать и следить за актуальностью указателей. К счастью, есть Scope, берущий на себя все эти заботы. Его методы позволяют легко и удобно выделять неспецифицированную память, память под массивы, структуры и C-строки, получать указатели на эту память, а так же автоматически освобождать её после завершения блока try-with-resources и менять состояние созданных указателей так, чтобы обращение к ним приводило к исключению, а не падению виртуальной машины.Чтобы посмотреть в работе структуры и указатели, усложним пример ещё немного.
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()); } } }
Интерес здесь представляет то, как объявляется интерфейс структуры и как под неё выделяется память. Обратите внимание, что в ldl-объявлении появился новый элемент — значения в круглых скобках после меток. Это аннотация метки в сокращённой форме. Полная форма выглядела бы так:
i32(name=x). Аннотация метки позволяет соотнести её с методом интерфейса.Прежде, чем переходить к обещанному в заголовке, осталось осветить ещё один способ взаимодействия с нативным кодом. Все предыдущие примеры вызывали нативные функции, но иногда нативному коду требуется вызывать java-код. Например, если мы хотим отсортировать массив с помощью
qsort, нам понадобиться callback.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(); } } }
Легко заметить, что ldl-объявления, и так не особо простые для восприятия, быстро превращаются в зубодробительные конструкции. А ведь
qsort — не самая сложная функция. Кроме того, в реальных библиотеках могут быть десятки структур и десятки функций, писать для них интерфейсы — дело неблагодарное. К счастью, обе проблемы легко решаются использованием утилиты jextract, которая сгенерирует всех необходимые интерфейсы на основе заголовочных файлов. Вернёмся к первому примеру и обработаем его этой утилитой.jextract -L . -l adder -o adder.jar -t "com.example" adder.h
// импорт jextract'нутых "заголовочных" классов import static com.example.adder_h.*; public class Example { public static void main(String[] args) { System.out.println(add(3, 5)); } }
И используем полученный jar-файл для сборки и запуска java-кода:
javac -cp adder.jar Example.java java -cp .;adder.jar Example
Пока не особенно впечатляет, но позволяет понять принцип. А теперь проделаем то же самое с python37.dll (наконец-то!)
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(); } }
Генерируем интерфейсы:
jextract -L "C:\Python37" -l python37 -o python.jar -t "org.python" --record-library-path C:\Python37\include\Python.h
Компилируем и запускаем:
javac -cp python.jar App.java java -cp .;python.jar App
Поздравляю, ваше java-приложение только что загрузило в себя интерпретатор Python и выполнило в нём скрипт!

Больше примеров можно посмотреть в инструкции для первопроходцев.
Maven-проекты с примерами из статьи можно найти на GitHub.
P.S. API сейчас находится в стадии бурных изменений. В презентациях вышедших пару месяцев назад докладов легко увидеть код, который не будет компилироваться. Не застрахованы от этого и примеры из этой статьи. Если вы столкнётесь с этим, отправьте мне сообщение, постараюсь исправить.
