几天前,我看到了Brian Goetz的一条推文,但直到今天,我的手才开始尝试使用示例。

我想简单地谈一谈。
关于哈布雷
的巴拿马工程 已经写过 。 要了解它是什么以及为什么,您应该在这里阅读采访。 我将仅展示几个有关如何使用
本机活页夹的简单示例。
首先,您需要C编译器,如果您使用的是Linux或MacOS,那么您已经有了C编译器。 如果使用Windows,则必须首先安装
Visual Studio 2017的构建工具 。 而且,当然,您需要具有巴拿马支持的OpenJDK。 您可以通过构建
相应存储库的“外国”分支或通过下载
Early-Access构建来获取它。
让我们从一个最小的示例开始-一个简单的函数,将两个数字相加:
加法器#ifndef _ADDER_H #define _ADDER_H __declspec(dllexport) int add(int, int); #endif
加法器 #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的人员,源应该有很多熟悉之处:声明本机库的接口,加载该库,将其连接到该接口,然后调用本机函数。 主要区别在于在批注中使用
布局定义语言来描述本机数据到Java类型的映射。
很容易猜到表达式“
(i32 i32)i32
”表示一个函数,该函数需要两个32位整数并返回一个32位整数。 标签
i
表示三种基本类型之一-具有小尾数字节顺序的整数。 除此之外,还经常找到
u
和
f
分别是无符号整数和浮点数。 相同的标签用于指示big-endian的顺序,但大写字母
I
,
U
,
F
另一个常见的标签是
v
,用于
void
。 标签后面的数字表示使用的位数。
如果数字在标签前面,则标签表示一个数组:
[42f32]
-由
float
类型的42个元素组成的数组。 方括号组标签。 除数组外,它还可用于指示结构(
[i32 i32]
-具有两个类型为
int
两个字段的结构)和联合(
[u64|u64:f32]
或
float
的指针)。
冒号用于指示指针。 例如,
u64:i32
是指向
int
的指针,
u64:v
是类型为
void
的指针,而
u64:[i32 i32]
是指向结构的指针。
有了这些信息,让我们使示例复杂一些。
累加器 __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
。 使用本机代码时,您将必须处理堆外数据,这意味着您将必须独立分配内存,独立释放和监视指针的相关性。 幸运的是,
Scope
所有这些问题。 它的方法可以轻松方便地分配未指定的内存,用于数组,结构和C线的内存,获取指向该内存的指针,并在try-with-resources块完成后自动释放它,并更改创建的指针的状态,以便进行调用它导致异常,而不是虚拟机崩溃。
为了看一下结构和指针的工作,让我们使这个例子复杂一些。
移动器 #ifndef _ADDER_H #define _ADDER_H typedef struct { int x; int y; } Point; __declspec(dllexport) void move(Point*); #endif
移动器 #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)
。 标签注释使您可以将其与接口方法相关联。
在继续标题中的Promise之前,仍然需要强调另一种与本机代码交互的方式。 前面所有示例都称为本机函数,但有时本机代码需要调用Java代码。 例如,如果要使用
qsort
对数组进行排序,则需要回调。
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
并使用生成的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上找到 。
PS API目前正在快速变化。 在几个月前的演示中,很容易看到无法编译的代码。 本文中的示例并非无法避免。 如果您遇到此问题,请给我发消息,我会尽力解决。