jdk.foreign
与 JDK 16
开始可用, 并处于测试状态.
代码在
Windows 10 Enterprise; 1909 (OS Build 18363.1679)
openjdk 17 2021-09-14; 64-Bit Server VM (build 17+35-2724, mixed mode, sharing)
下测试通过
Environment prepare
开始一切之前, 需要准备好所有的一切东西
- Java IDE
- JDK 16+
- C/CPP IDE (Optional)
Project initiation
首先,新建文件夹, 然后点击 Win, 点击关机, 使用 Java IDE 打开该文件夹.
创建以下文件
1 2 3 4 5 6
| distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists
|
1 2 3 4 5 6 7 8 9 10 11 12
| plugins { id 'java' id 'java-plugin' } repositories { mavenCentral() } dependencies {} compileJava { options.compilerArgs += ['--add-modules', 'ALL-SYSTEM'] }
|
1 2 3 4 5 6
| package pkg; public class Boot { public static void main(String[] $$$$$$$$$$$$$$$$) throws Throwable { } }
|
进入 Project Structure > Project
将 Project SDK
切换至 JDK 16+
右键 build.gradle
导入 gradle 项目, 等待导入完成后
右键 pkg.Boot
的入口点, 添加 jvm 参数 --add-modules ALL-SYSTEM --enable-native-access ALL-UNNAMED
Code
根据 JEP 419: Foreign Function & Memory API 的 Example, 可以很快写出看上去可以运行的代码.
使用 jdk.foreign
调用本机代码的步骤共三步, 本机方法寻址
, 方法签名拼接
, 执行
.
Setup
第一步需要先获得相关的 api 对象, 以供我们完成桥接.
1 2 3
| var linker = CLinker.getInstance(); var symbolLookup = SymbolLookup.loaderLookup(); var systemLookup = CLinker.systemLookup();
|
Native function symbol lookup
需要先拿到方法地址 (指针), 才能执行本机方法, JDK 提供了搜索方法符号的 api.
寻址时需要的是实际名字, 而不是在 ****.h
中使用 #define
定义的别名 (比如 SetWindowLong
实际应该为 SetWindowLongA
)
1 2 3 4 5
| var system = systemLookup.lookup("system") .or(() -> symbolLookup.lookup("system")) .orElseThrow(); System.out.println(system);
|
Function description mapping.
只有正确告诉 JVM 我们要执行的方法的签名是什么样的, JVM 才能正确处理相关的访问, 否则 JVM Crashed
.
如无必要,切勿直接使用 MemoryLayout 中的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var funcDesc = FunctionDescriptor.of( CLinker.C_INT, CLinker.C_POINTER ); var mh = linker.downcallHandle( MethodType.methodType( int.class, MemoryAddress.class ), funcDesc ); System.out.println(mh);
|
Invoke native function
system
方法需要一个指针 (char*), 由于 jdk 不允许直接获得一个方法对象的指针, 所以需要将相关内容拷贝到堆外内存.
其中 jdk.foreign
提供了 ResourceScope
防止内存泄露.
1 2 3 4 5 6 7 8 9 10
| try (var scope = ResourceScope.newConfinedScope()) { var allocator = SegmentAllocator.ofScope(scope); var cmd_java = "whoami".getBytes(StandardCharsets.UTF_8); var cmd_cnative = allocator.allocate(cmd_java.length + 1); cmd_cnative.copyFrom(MemorySegment.ofArray(cmd_java)); MemoryAccess.setByAtOffset(cmd_cnative, cmd_java.length, (byte) 0 );
var $ = (int) mh.invokeExact((Addressable) system, cmd_cnative.address()); System.out.println($); }
|
Full code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| package pkg;
import jdk.incubator.foreign.*;
import java.lang.invoke.MethodType; import java.nio.charset.StandardCharsets;
public class Boot { public static void main(String[] args) throws Throwable { var linker = CLinker.getInstance(); var symbolLookup = SymbolLookup.loaderLookup(); var systemLookup = CLinker.systemLookup(); var system = systemLookup .lookup("system") .or(() -> symbolLookup.lookup("system")) .orElseThrow(); System.out.println(system); var funcDesc = FunctionDescriptor.of( CLinker.C_INT, CLinker.C_POINTER ); var mh = linker.downcallHandle(MethodType.methodType( int.class, MemoryAddress.class ), funcDesc); System.out.println(mh); try (var scope = ResourceScope.newConfinedScope()) { var allocator = SegmentAllocator.ofScope(scope); var cmd_java = "whoami".getBytes(StandardCharsets.UTF_8); var cmd_cnative = allocator.allocate(cmd_java.length + 1); cmd_cnative.copyFrom(MemorySegment.ofArray(cmd_java)); MemoryAccess.setByteAtOffset(cmd_cnative, cmd_java.length, (byte) 0 );
var $ = (int) mh.invokeExact((Addressable) system, cmd_cnative.address()); System.out.println($); } } }
|