最近在整理学习笔记的时候发现了去年年中记录的 JNI 学习笔记,由于存放在了为知笔记中,而如今为知笔记已经不再免费,于是想到了将其重新整理一遍,一来可以巩固所学,二来能将其迁移到本地
1 简介
有时候,使用 native code(c/c++)来克服 Java 中的内存管理和性能的局限性是很有必要的。Java 支持 native codes,被称作 Java Native Interface(JNI)。
JNI 非常难,毕竟它牵涉到了两种编程语言。假设聪明的你对 Java 和 C/C++以及 GCC 编译器已经有所了解。那么下面就一起来一步步学习 JNI 吧。
2 开始
2.1 用 c 语言实现第一个 JNI 程序
Step1: 创建一个名字为 JNITest.java 的文件
public class JNITest {
static {
System.load("/home/ubuntu/workspace/java/jni/mynativelib.so");
}
//申明一个无参的native方法,而且返回空
public native void greet();
//测试
public static void main(String[] args) {
JNITest test = new JNITest();
test.greet();
}
}
首先用静态代码块加载本地动态链接库"mynativelib.so"。对于静态代码块,我相信写过 Java 的你应该非常清楚,它只会在类被加载的时候执行一次。这个动态链接库会被添加到 Java 的 library path(保存在 Java 系统变量 java.library.path)中,如果加载失败,就会抛出UnsatisfiedLinkError
异常。也可以使用 JVM 启动参数来加载该动态链接库到 Java 的 library path 当中:
-Djava.library.path=path_to_lib
说明:JNI 中有两种常见的加载动态链接库的写法,一个是System.load(filename)
,另一个是System.loadLibrary(libname)
,二者的区别在于,第一种需要的是文件的全名称,而第二种只需要动态链接库的库名(windows 下省略.dll 后缀,linux 下省略 lib 前缀和.so 后缀)。
然后我们使用native
关键字申明一个名为greet
的 native instance method。被 native 修饰的方法不能有方法体。
接下来编译 JNITest.java 文件
javac JNITest.java
Step2: 生成 C/C++头文件 JNITest.h
javah JNITest
执行上述命令后自动生成一个名为 JNITest.h 的文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNITest */
#ifndef _Included_JNITest
#define _Included_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JNITest
* Method: greet
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_JNITest_greet
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
该头文件声明了Java_JNITest_greet
的函数原型,Java 中的函数对应到 C 中的命名规则为:Java_{package_and_classname}_{method_name}(JNI arguments)
其中 JNI arguments:
- JNIEnv *: 指向 JNI 执行环境的指针,通过它可以访问到所有的 JNI 方法
- jobject: 指向 Java 中的"this"
**Step3:**用 c 语言实现头文件中的函数 - jnitest.c
#include <stdio.h>
#include <jni.h>
#include "JNITest.h"
JNIEXPORT void JNICALL Java_JNITest_greet(JNIEnv *env, jobject obj) {
printf("hello liubang...\n");
printf("this is implemented by c and called by java!\n");
return;
}
**Step4:**编译动态链接库
gcc -fPIC -shared -I /opt/app/java/include/ -I /opt/app/java/include/linux/ jnitest.c -o mynativelib.so
**Step5:**运行 Java 程序
java JNITest
====output===
ubuntu@vm-911:~/workspace/java/jni$ java JNITest
hello liubang...
this is implemented by c and called by java!
于是第一个 c 语言实现的 JNI 程序就跑起来了,是不是很简单。别着急,这只是个开始!
2 JNI in Package
通常情况下,几乎所有的 Java 类都有自己的 package 而不是直接使用默认的无名的 package。那么对于使用了 package 的 Java 类如何来创建 JNI 程序呢,其实跟开始的例子几乎是一样的。
首先实现一个 test.HelloJNI 类
package test;
public class HelloJNI {
static {
System.loadLibrary("hello");
}
private native void sayHello();
public static void main(String[] args) {
new HelloJNI().sayHello();
}
}
然后编译 Java 程序
javac test/HelloJNI.java
接着生成 C/C++头文件
//将生成的头文件存放在include文件夹
javah -d include test.HelloJNI
实现头文件中定义的函数
#include <stdio.h>
#include <jni.h>
#include "include/test_HelloJNI.h"
JNIEXPORT void JNICALL Java_test_HelloJNI_sayHello(JNIEnv *env, jobject this) {
printf("hello liubang\n");
return;
}
编译成动态链接库
gcc -fPIC -shared -I /opt/app/java/include/ -I /opt/app/java/include/linux/ HelloJNI.c -o libhello.so
然后运行 Java 程序
java -Djava.library.path=. test.HelloJNI
====output====
ubuntu@vm-911:~/workspace/java/jni/demo1$ java -Djava.library.path=. test.HelloJNI
hello liubang
评论