初识JNI

一,概念

JNI是Java Native Interface的缩写,即Java本地接口的意思。JNI技术实现了Java程序和本地应用程序的相互调用,通常这里的本地程序指的是使用C或C++语言编写的应用程序。从JNI的名称我们可以知道,这一技术是通过接口来实现的。接口编程的好处就是,可扩展性高,同时也能保证Java跨平台的特性。使用JNI有以下几点好处,一可以直接使用之前已经定义好的Naitive模块,避免重复的造轮子;二可以提高程序的运行效率和速度;三可以提高程序安全性,增加被反编译的难度。

二,使用

1,加载JNI库

如果Java要调用Native函数,就必须通过一个位于JNI层的动态库才能做到。顾名思义,动态库就是运行时加载的库,那么是什么时候,在什么地方加载这个库呢?
这个问题没有标准答案,原则上是在调用Native函数前,任何时候、任何地方加载都可以。通行的做法是,在类的static语句中加载,通过调用System.loadLibrary方法就可以了。System.loadLibrary函数的参数是动态库的名字,系统会自动根据不同的平台拓展成真实的动态库文件名,例如在Linux系统上会拓展成libname.so,而在Windows平台上则会拓展成libname.dll。具体请看下面代码的演示:

// 加载JNI库
static {
    System.loadLibrary("hello_jni");
}

2,声明Native函数

Java提供了native关键字,用于声明一个Native函数,和声明一个接口一样,我们并不需要为Native函数提供具体实现。

// 声明一个Native函数。native为Java的关键字,表示它将由JNI层完成。
public static native void sayHello();

3,调用Native函数

和正常的Java函数调用一样,直接调用即可。至此Java层代码已经编写完毕,代码如下:

public class HelloJNI {

    // 加载JNI库
    static {
        System.loadLibrary("hello_jni");
    }

    // 声明一个Native函数。native为Java的关键字,表示它将由JNI层完成。
    public static native void sayHello();

    // 在main函数中调用Native函数
    public static void main(String[] args) {
        sayHello();
    }
}

4,编译生成头文件

1)进入HelloJNI.java所在的目录下,使用javac HelloJNI.java命令编译Java源文件
2)使用javah HelloJNI命令在当前目录下生成HelloJNI.h头文件
3)下面是linux系统上的操作演示:

yqm@unbuntu:~$ cd Desktop/JNIDemo/
yqm@unbuntu:~/Desktop/JNIDemo$ javac HelloJNI.java 
yqm@unbuntu:~/Desktop/JNIDemo$ ll
总用量 16
drwxrwxr-x 2 yqm yqm 4096  57 09:14 ./
drwxr-xr-x 9 yqm yqm 4096  57 09:13 ../
-rw-rw-r-- 1 yqm yqm  441  57 09:14 HelloJNI.class
-rw-rw-rw- 1 yqm yqm  364  57 09:13 HelloJNI.java
yqm@unbuntu:~/Desktop/JNIDemo$ javah HelloJNI
yqm@unbuntu:~/Desktop/JNIDemo$ ll
总用量 20
drwxrwxr-x 2 yqm yqm 4096  57 09:14 ./
drwxr-xr-x 9 yqm yqm 4096  57 09:13 ../
-rw-rw-r-- 1 yqm yqm  441  57 09:14 HelloJNI.class
-rw-rw-r-- 1 yqm yqm  372  57 09:14 HelloJNI.h
-rw-rw-rw- 1 yqm yqm  364  57 09:13 HelloJNI.java
yqm@unbuntu:~/Desktop/JNIDemo$ cat HelloJNI.h 
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJNI
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJNI_sayHello
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif
yqm@unbuntu:~/Desktop/JNIDemo$ 

4,用C或C++实现Native函数

可以看到使用javah命令自动生成的头文件,已经为我们在Java代码中定义的Native函数生成了对应的C和C++函数声明。所以,我们只要使用C或C++引入头文件并实现对应的函数即可。

// filename -- HelloJNI.cpp
// 包含自动生成的头文件
#include "HelloJNI.h"
#include <iostream>

JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jclass)
{
    using namespace std;

    cout << "From HelloJNI.cpp :"
        << "Hello world !"
        << endl;

    return;
}

5,编译动态库

C++编译动态库,使用命令g++ -shared -fPIC -I <JAVA_HOME_DIR>/include -I <JAVA_HOME_DIR>/include/linux <filename>.cpp -o lib<filename>.so。其中<JAVA_HOME_DIR>需要使用本机的jdk的安装目录进行替换,<filename>需要使用cpp文件名称替换。linux系统上的操作演示:

yqm@unbuntu:~$ cd Desktop/JNIDemo/
yqm@unbuntu:~/Desktop/JNIDemo$ ll
总用量 24
drwxrwxr-x 2 yqm yqm 4096  57 10:14 ./
drwxr-xr-x 9 yqm yqm 4096  57 09:13 ../
-rw-rw-r-- 1 yqm yqm  441  57 09:14 HelloJNI.class
-rw-rw-rw- 1 yqm yqm  265  57 10:12 HelloJNI.cpp
-rw-rw-r-- 1 yqm yqm  372  57 09:14 HelloJNI.h
-rw-rw-rw- 1 yqm yqm  364  57 10:11 HelloJNI.java
yqm@unbuntu:~/Desktop/JNIDemo$ g++ -shared -fPIC -I /home/yqm/EnvTools/java-1.8.0-openjdk-amd64/include -I /home/yqm/EnvTools/java-1.8.0-openjdk-amd64/include/linux HelloJNI.cpp -o libhello_jni.so
yqm@unbuntu:~/Desktop/JNIDemo$ ll
总用量 36
drwxrwxr-x 2 yqm yqm 4096  57 10:20 ./
drwxr-xr-x 9 yqm yqm 4096  57 09:13 ../
-rw-rw-r-- 1 yqm yqm  441  57 09:14 HelloJNI.class
-rw-rw-rw- 1 yqm yqm  265  57 10:12 HelloJNI.cpp
-rw-rw-r-- 1 yqm yqm  372  57 09:14 HelloJNI.h
-rw-rw-rw- 1 yqm yqm  364  57 10:11 HelloJNI.java
-rwxrwxr-x 1 yqm yqm 8594  57 10:20 libhello_jni.so*
yqm@unbuntu:~/Desktop/JNIDemo$ 

6,运行

使用命令java -Djava.library.path=<LIBDIR> <ProgramName>来运行程序。其中,<LIBDIR>需要使用存放上面生成的.so的路径来替换,<ProgramName>需要使用你所运行的程序名称来替换。linux系统上的操作演示:

yqm@unbuntu:~/Desktop/JNIDemo$ java -Djava.library.path=. HelloJNI
From HelloJNI.cpp :Hello world !
yqm@unbuntu:~/Desktop/JNIDemo$ 

三,总结

上面通过一个简单的例子,展示了使用JNI的一般步骤。在Java程序中,我们只需要定义Native函数声明,然后去使用即可,唯一需要注意的就是,使用前我们需要加载JNI库。在C和C++程序中,我们只需要引入自动生成的头文件,并实现头文件中声明的与Java程序中对应的函数,然后编译生成动态库即可。

四,说明

1,g++参数说明

1)-shared:指定生成动态链接库
2)-fPIC:编译器就输出位置无关目标码.适用于动态连接(dynamic linking)
3)-I <dir>:在你使用用#include”file”的时候,gcc/g++会先在当前目录查找你所制定的文件,如果没有找到,它会到缺省的头文件目录找,如果使用-I指定了目录,它会先在你所指定的目录查找,然后再按常规的顺序去找。上面我们使用了两个-I参数先后指定了两个目录,它们分别包含了jni.h头文件和jni_md.h头文件。
4)-o <outputFileName>:指定目标名称,缺省的时候,gcc 编译出来的文件名称是a.out。需要注意的是,在linux系统上这里我们指定的名称,必须以lib为前缀以.so为后缀。而在Java程序中加载JNI库时,不需要明确指出前缀和后缀。

2,java -Djava.library.path=<libpath>说明

我们需要通过-Djava.library.path=<libpath>参数,来显示指定程序所包含的动态库的位置。

仅供参考,欢迎指正。

猜你喜欢

转载自blog.csdn.net/miracle_yan/article/details/80221025
JNI