Java 17 实现 cls 清屏功能(与 jni 比较)

1,237 阅读2分钟

前言

以往如果需要调用C/C++代码去实现一些底层的操作,需要使用JNI(Java Native Interface)。就以cls清屏功能为例,需要自己编写相应的C/C++并生成dll文件,然后再通过JNI的方式调用dll来实现相应的功能,具体步骤可以参考这篇博客:使用JNA在Java中实现cls(命令行清屏)功能,这里展示了如何使用基于JNIJNA框架来调用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,然后找到一下配置界面:

image-20211120160431098

其中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

image-20211120160743378

完成以上配置,即可以体验cls清屏功能了,不过使用IDEArun窗口运行会出现乱码,所以需要选择Terminal窗口,或者使用cmd命令窗口,在项目的根目录下输入以下命令即可(out\production\jni中的jni需要替换为自己的项目名):

java --add-modules jdk.incubator.foreign --enable-native-access ALL-UNNAMED -classpath out\production\jni Main

jna 方式实现

下面再简单介绍如果使用基于jnijna实现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("清屏成功");
    }

}

效果如下:

动画