JNI 简介

众所周知,Java 的主要优势之一是它的可移植性,这意味着一旦我们编写并且编译了代码,这个过程的结果就是不依赖于平台的字节码。它可以像我们预期的那样运行在任何能够运行 Java 虚拟机的机器或设备上。
但是,有时我们确实需要使用一些为某些特定架构而进行本地编译的原生代码。例如:
需要对硬件执行某些操作
对性能要求非常苛刻
想要重用的现有库,而不是用 Java 重写它。
为了实现这一点,JDK 在我们的 JVM 中运行的字节码和原生代码(通常用 C 或 C++ 编写)之间搭建了一座桥梁。该桥梁就称为Java Native Interface。

JNI 如何工作


Java 提供了 native 关键字,用于指明该方法的实现将由原生代码提供。native 关键字将我们的方法转换为一种抽象方法:

  1. private native void aNativeMethod();


在这里,这个方法的实现不是由另一个 Java 类实现,而是在一个分离的原生动态共享库中实现。它将在内存中构造一个表,其中包含指向我们所有原生方法实现的指针,以便可以从 Java 代码中调用它们。

让 JNI 工作起来所需要的一些关键组件如下:

  1. Java 代码 - 我们的类,它将至少包含一种本地方法。
  2. 原生代码 - 我们原生代码的实际逻辑,通常使用 C 或者 C++ 代码。
  3. JNI 头文件 - 这个 C/C++ 的头文件 (jni.h),包括了我们可以在原生程序中使用的所有JNI 元素。
  4. C/C++ 编译器 - 用于为我们的平台生成原生共享库。


代码中的 JNI 组件包括了 Java 和 C/C++ 代码。

1. Java 代码:

  • “native” 关键字 - 标记为 native 的方法都必须在原生共享库中实现。
  • System.loadLibrary(String libname) - 一种静态方法,用于将共享库从文件系统加载到内存中,并使其包含的函数可用于我们的 Java 代码。

2. C/C++ 代码:

  • JNIEXPORT - 将共享库中的函数标记为可导出,它将包含在函数表中,因此 JNI 可以找到它。
  • JNICALL - 与 JNIEXPORT 结合使用,确保我们的方法可用于 JNI 框架。
  • JNIEnv - 一个包含方法的结构,可以使用我们的原生代码访问 Java 元素。
  • JavaVM - 一种让我们可以操纵正在运行的 JVM(甚至启动一个新的 JVM)的结构,向它添加线程、销毁它等等。

    3. 编写一个HelloWorld

    1. 创建Java类

    受限编写我们的 Java 代码 HelloWorldJNI.java,具体如下,此类中用 native 关键字定义了需要 C/C++ 实现的原生方法 sayHello()

    1. public class HelloWorldJNI {
    2. static {
    3. System.loadLibrary("native");
    4. }
    5. public static void main(String[] args) {
    6. new HelloWorldJNI().sayHello();
    7. }
    8. // 定义原生sayHello()方法
    9. private native void sayHello();
    10. }

    2.通过 Java 类生成 C/C++ 所需的头文件

    通过 Java 类自动生成 sayHello() 方法的定义,并保存在 HelloWorldJNI.h 头文件中

    1. javac -h . HelloWorldJNI.java

    自动生成的 HelloWorldJNI.h 的头文件内容如下: ```c / DO NOT EDIT THIS FILE - it is machine generated /

    include

    / Header for class HelloWorldJNI /

ifndef _Included_HelloWorldJNI

define _Included_HelloWorldJNI

ifdef __cplusplus

extern “C” {

endif

/*

  • Class: HelloWorldJNI
  • Method: sayHello
  • Signature: ()V / JNIEXPORT void JNICALL Java_HelloWorldJNI_sayHello (JNIEnv , jobject);

ifdef __cplusplus

}

endif

endif

函数名是使用完全限定的包名、类名和方法名自动生成的。这个函数有两个参数,1 个是指向当前 JNIEnv 的指针,另一个是该方法附加到的 Java 对象,HelloWorldJNI 类的实例。
<a name="GOJ2I"></a>
## 3. 编写 C/C++ 文件,完成 sayHello() 的函数实现。
需要为 sayHello 函数的实现创建一个新的 c/cpp 文件,文件里面包含了函数的具体实现。将此文件和 .h 使用相同的命名。<br />以 C 代码举例,HelloWorldJNI.c 的具体实现如下:
```c
#include <stdio.h>
#include "HelloWorldJNI.h"

JNIEXPORT void JNICALL Java_HelloWorldJNI_sayHello
  (JNIEnv* env, jobject thisObject) {
    printf("hello world!\n");
    // All Code here
}

4. 编译C代码,生成共享库

gcc -shared -fPIC -o libnative.so -I D:\Environment\jdk-15.0\include -I D:\Environment\jdk-15.0\include\win32 HelloWorldJNI.c

image.png

5. 运行Java程序

java -cp . -Djava.library.path=. HelloWorldJNI

https://blog.csdn.net/yaojingqingcheng/article/details/123497697