前言
以往如果需要调用C/C++
代码去实现一些底层的操作,需要使用JNI
(Java Native Interface
)。就以cls
清屏功能为例,需要自己编写相应的C/C++
并生成dll
文件,然后再通过JNI
的方式调用dll
来实现相应的功能,具体步骤可以参考这篇博客:使用JNA在Java中实现cls(命令行清屏)功能,这里展示了如何使用基于JNI
的JNA
框架来调用dll
文件去实现cls
清屏功能。而在Java 17
中,我们便不再需要编写C/C++
代码以及生成dll
文件,而仅仅只需要写Java
代码即可(目前还处于孵化器阶段),下面展示具体的使用方法。
实现
在介绍之前,先展示最终的代码:
import jdk.incubator.foreign.CLinker;
import jdk.incubator.foreign.FunctionDescriptor;
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.ResourceScope;
import java.lang.invoke.MethodType;
import static jdk.incubator.foreign.CLinker.C_POINTER;
/**
* 清屏测试类
*
* @author butterfly
* @date 2021-11-14
*/
public class Main {
public static void main(String[] args) throws Throwable {
// 简单跑马灯对照效果
var tip = "等待清屏...";
var begin = 0;
var end = 10;
var interval = 1000;
var backspaces = "\b".repeat(tip.length());
while (begin++ < end) {
tip = tip.substring(1) + tip.charAt(0);
System.out.print(backspaces + tip);
Thread.sleep(interval);
cls();
}
System.out.print("清屏成功");
}
/**
* 清屏方法
*/
private static void cls() throws Throwable {
// 获取 system 函数
var systemFunction = CLinker.systemLookup().lookup("system").orElseThrow();
// 标识 system 函数的返回值及参数类型
var systemHandle = CLinker.getInstance().downcallHandle(
systemFunction,
MethodType.methodType(void.class, MemoryAddress.class),
FunctionDescriptor.ofVoid(C_POINTER)
);
// 获取存储在本地内存段中的 "cls" 字符串, 用于执行 system("cls") 清屏函数
var clsStr = CLinker.toCString("cls", ResourceScope.newImplicitScope());
// 执行 system("cls") 清屏函数
systemHandle.invoke(clsStr.address());
}
}
可以发现我们现在可以直接在Java
中调用C/C++
中的system
函数并执行cls
清屏命令,编写的代码也不算太多,这里结合注释也能够基本理解。
效果
下面再展示上述代码的实际效果:
配置
由于该功能仍然处于孵化器阶段,所以想要体验的话,还需要进行一些配置,下面已IDEA
的配置为例进行讲解:
首先选择File -> Settings
,然后找到一下配置界面:
其中Compilation options
中的配置内容如下:
--add-modules jdk.incubator.foreign --add-exports java.base/jdk.internal.access=ALL-UNNAMED
然后加入如下的VM options
参数:
--add-modules jdk.incubator.foreign --enable-native-access ALL-UNNAMED
完成以上配置,即可以体验cls
清屏功能了,不过使用IDEA
的run
窗口运行会出现乱码,所以需要选择Terminal
窗口,或者使用cmd
命令窗口,在项目的根目录下输入以下命令即可(out\production\jni
中的jni
需要替换为自己的项目名):
java --add-modules jdk.incubator.foreign --enable-native-access ALL-UNNAMED -classpath out\production\jni Main
jna 方式实现
下面再简单介绍如果使用基于jni
的jna
实现cls
的清屏功能,详细步骤可以参考使用JNA在Java中实现cls(命令行清屏)功能,这里不再展示dll
文件(点击这里下载)的生成步骤:
pom
的配置如下:
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<jna.version>5.10.0</jna.version>
</properties>
<dependencies>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>${jna.version}</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>${jna.version}</version>
</dependency>
</dependencies>
代码结构如下:
└─src
└─main
├─java
├──── Cls.java
├──── Main.java
│
└─resources
├──── clsscreen.dll
其中clsscreen.dll
即为上文提到要下载的文件。
Cls.java
内容如下:
import com.sun.jna.Library;
import com.sun.jna.Native;
import java.util.Objects;
/**
* cls 清屏方法接口
*
* @author butterfly
* @date 2021-11-20
*/
public interface Cls extends Library {
/**
* dll 文件名
*/
String DLL_FILE_NAME = "clsscreen.dll";
/**
* 读取 clsscreen.dll 文件
*/
Cls DLL = Native.load(
Objects.requireNonNull(Cls.class.getResource(DLL_FILE_NAME)).getPath().substring(1), Cls.class);
/**
* cls 清屏方法
*/
void cls();
}
Main.java
的内容如下:
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* jna 实现 cls 清屏功能测试
*
* @author zjw
* @date 2021-11-20
*/
public class Main {
public static void main(String[] args) throws InterruptedException {
// 简单跑马灯对照效果
String tip = "等待清屏...";
int begin = 0;
int end = 10;
int interval = 1000;
String backspaces = Stream.generate(() -> "\b")
.limit(tip.length())
.collect(Collectors.joining());
while (begin++ < end) {
tip = tip.substring(1) + tip.charAt(0);
System.out.print(backspaces + tip);
Thread.sleep(interval);
Cls.DLL.cls();
}
System.out.print("清屏成功");
}
}
效果如下: