JNI入门之HelloWorld

本文通过编写一个简单的 HelloWorld 带你熟悉下 JNI,包括如何编写一个 JNI 方法,如何打包成动态链接库,如何加载并调用等。

通常学习一项技术的第一步是从 HelloWorld 开始的,在开始前有必要先了解下什么是 JNI。

什么是 JNI

JNI 全称是 Java Native Interface,即 Java 本地编程接口。根据 JNI 规范,java 程序可以调用使用其他编程语言(例如C、C++、汇编语言等)编写的程序或类库。并且它是一个通用的 java 程序与本地方法交互的规范,对于符合要求的本地(Native)应用程序或类库,可在支持该规范的不同 java 虚拟机上正常运行。

编写 JNI 版 HelloWorld

下面就来写一个 JNI 版的 HelloWorld。主要的流程为:

  1. 创建 java 类,声明 native 方法
  2. 生成 native 方法的头文件
  3. 编写 native 方法的实现,生成动态链接库文件
  4. 在 java 中加载动态链接库并调用 native 方法

声明 native 方法

创建类 HelloWorldNative,声明 native 方法 void sayHello()

1
2
3
4
5
6
7
public class HelloWorldNative {

/**
* 这是一个native方法
*/
public native void sayHello();
}

生成头文件

有了 java 类里的 native 方法声明后,就可以使用 jdk 提供的工具生成 c 语言的头文件。

  1. 执行 javac HelloWorldNative.java 命令, 编译 java 类,生成字节码文件

    1
    2
    3
    ➜  javac HelloWorldNative.java
    ➜ ls
    HelloWorldNative.class HelloWorldNative.java
  2. 执行 javah HelloWorldNative 命令,生成头文件 HelloWorldNative.hjavah 是 jdk 自带的命令,可用于根据 java class 文件生成 c 语言的头文件(header),详细介绍可参考命令的 man page。

    1
    2
    3
    ➜  javah HelloWorldNative
    ➜ ls
    HelloWorldNative.class HelloWorldNative.h HelloWorldNative.java

    生成的头文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class HelloWorldNative */

    #ifndef _Included_HelloWorldNative
    #define _Included_HelloWorldNative
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
    * Class: HelloWorldNative
    * Method: sayHello
    * Signature: ()V
    */
    JNIEXPORT void JNICALL Java_HelloWorldNative_sayHello
    (JNIEnv *, jobject);

    #ifdef __cplusplus
    }
    #endif
    #endif

    对比下 java 里方法的定义为 void sayHello(),转换后为 void JNICALL Java_HelloWorldNative_sayHello(JNIEnv *, jobject),其转换规则如下

    • 前缀为 Java_
    • 完全限定的类名(包括包名和类的全路径),中间以 _ 分割
    • 方法名
    • 对于重载的 native 方法,方法名后要再跟上 __参数标签

编写 native 方法

因为是 JNI 方法,所以需要引入 jni.h,然后还要引入刚生成的 HelloWorld.h。为了在控制台打印 HelloWorld,我们再引入 stdio.h。最后是将头文件 HelloWorldNative.h 里的方法声明复制过来,加上实现。注意方法的入参需手动加上名字,否则会报 error: parameter name omitted 错误。

1
2
3
4
5
6
7
8
9
#include <jni.h>
#include <stdio.h>
#include "HelloWorldNative.h"

JNIEXPORT void JNICALL Java_HelloWorldNative_sayHello
(JNIEnv *env, jobject object) {
printf("HelloWorld!\n");
return;
}

编译生成库文件

这里使用的是 mac os 系统,jdk 安装在 /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk 目录下。使用 gcc 来编译 HelloWorldNative.c 并生成库文件,用到的参数如下:

  1. 指定 JNI 相关的头文件路径

    目的是告诉 gcc 编译器去哪里查找 JNI 相关的 jni.h 等头文件。使用 -I 参数指定路径

    1
    2
    -I /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/include
    -I /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/include/darwin
  2. 指定生成动态链接库文件

    使用 -shared 参数指定生成的是动态链接库,使用 -o 参数指定生成的动态链接库文件名

    1
    -shared -o libhello.so

最终执行的命令为

1
gcc -I /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/include -I /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/include/darwin HelloWorldNative.c -shared -o libhello.so

加载并调用 JNI 方法

生成的动态链接库要加载后才能使用,这里使用 System.load() 方法从绝对路径下加载到 JVM 中,然后就可以使用其提供的方法。相关代码如下:

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
// 从当前路径下加载库文件
System.load(Test.class.getResource("").getPath() + "/libhello.so");
new HelloWorldNative().sayHello();
}
}
  1. 编译成字节码

    1
    javac Test.java
  2. 执行

    1
    java Test

    输出结果如下

    1
    2
    ➜ java Test
    HelloWorld!

    可使用 -verbose:jni 参数查看使用的 native 方法。可以看到动态链接了 native 方法 HelloWorldNative.sayHello

    1
    2
    3
    4
    5
    6
    7
    8
    ➜  java -verbose:jni Test
    [Dynamic-linking native method java.lang.Object.registerNatives ... JNI]
    [Registering JNI native method java.lang.Object.hashCode]
    [Registering JNI native method java.lang.Object.wait]
    [Registering JNI native method java.lang.Object.notify]
    ...
    [Dynamic-linking native method HelloWorldNative.sayHello ... JNI]
    HelloWorld!

上面用到的所有文件如下,至此我们已经完成了 JNI 版 HelloWorld 的编写和调用。

1
2
3
4
5
6
7
├── HelloWorldNative.c
├── HelloWorldNative.class
├── HelloWorldNative.h
├── HelloWorldNative.java
├── Test.class
├── Test.java
└── libhello.so

参考

Java Native Interface Specification