最近在整理学习笔记的时候发现了去年年中记录的 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 的文件
1public class JNITest {
2 static {
3 System.load("/home/ubuntu/workspace/java/jni/mynativelib.so");
4 }
5
6 //申明一个无参的native方法,而且返回空
7 public native void greet();
8
9 //测试
10 public static void main(String[] args) {
11 JNITest test = new JNITest();
12 test.greet();
13 }
14}
首先用静态代码块加载本地动态链接库"mynativelib.so"。对于静态代码块,我相信写过 Java 的你应该非常清楚,它只会在类被加载的时候执行一次。这个动态链接库会被添加到 Java 的 library path(保存在 Java 系统变量 java.library.path)中,如果加载失败,就会抛出UnsatisfiedLinkError
异常。也可以使用 JVM 启动参数来加载该动态链接库到 Java 的 library path 当中:
1-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 文件
1javac JNITest.java
Step2: 生成 C/C++头文件 JNITest.h
1javah JNITest
执行上述命令后自动生成一个名为 JNITest.h 的文件
1/* DO NOT EDIT THIS FILE - it is machine generated */
2#include <jni.h>
3/* Header for class JNITest */
4
5#ifndef _Included_JNITest
6#define _Included_JNITest
7#ifdef __cplusplus
8extern "C" {
9#endif
10/*
11 * Class: JNITest
12 * Method: greet
13 * Signature: ()V
14 */
15JNIEXPORT void JNICALL Java_JNITest_greet
16 (JNIEnv *, jobject);
17
18#ifdef __cplusplus
19}
20#endif
21#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
1#include <stdio.h>
2#include <jni.h>
3#include "JNITest.h"
4
5JNIEXPORT void JNICALL Java_JNITest_greet(JNIEnv *env, jobject obj) {
6 printf("hello liubang...\n");
7 printf("this is implemented by c and called by java!\n");
8 return;
9}
**Step4:**编译动态链接库
1gcc -fPIC -shared -I /opt/app/java/include/ -I /opt/app/java/include/linux/ jnitest.c -o mynativelib.so
**Step5:**运行 Java 程序
1java JNITest
2
3====output===
4ubuntu@vm-911:~/workspace/java/jni$ java JNITest
5hello liubang...
6this is implemented by c and called by java!
于是第一个 c 语言实现的 JNI 程序就跑起来了,是不是很简单。别着急,这只是个开始!
2 JNI in Package
通常情况下,几乎所有的 Java 类都有自己的 package 而不是直接使用默认的无名的 package。那么对于使用了 package 的 Java 类如何来创建 JNI 程序呢,其实跟开始的例子几乎是一样的。
首先实现一个 test.HelloJNI 类
1package test;
2
3public class HelloJNI {
4
5 static {
6 System.loadLibrary("hello");
7 }
8
9 private native void sayHello();
10
11 public static void main(String[] args) {
12 new HelloJNI().sayHello();
13 }
14
15}
然后编译 Java 程序
1javac test/HelloJNI.java
接着生成 C/C++头文件
实现头文件中定义的函数
1#include <stdio.h>
2#include <jni.h>
3#include "include/test_HelloJNI.h"
4
5JNIEXPORT void JNICALL Java_test_HelloJNI_sayHello(JNIEnv *env, jobject this) {
6 printf("hello liubang\n");
7 return;
8}
编译成动态链接库
1gcc -fPIC -shared -I /opt/app/java/include/ -I /opt/app/java/include/linux/ HelloJNI.c -o libhello.so
然后运行 Java 程序
评论