一般,在开发 JNI 程序时,咱们都会使用 IDE,例如 Eclipse , Android Studio , 这是由于工具简化了开发的流程,提高了工做效率,可是却让咱们愈来愈看不清本质的东西。所以,本篇文章就不使用 IDE 来作一次 JNI 开发,这样咱们就能够对原生 JNI 有个更全面的了解。php
本文的例子是在 Ubuntu 16.04 下运行的。html
首先咱们新建一个项目目录叫 JNIDemo
,而后进入这个目录java
bxll:~$ mkdir JNIDemo
bxll:~$ cd JNIDemo/
复制代码
在 JNIDemo
目录下,新建一个叫 Hello.java
的文件,代码内容以下linux
package com.bxll.jnidemo;
public class Hello {
static {
System.loadLibrary("hello_jni");
}
static native String helloFromJNI();
public static void main(String[] args) {
System.out.println(helloFromJNI());
}
}
复制代码
首先,在静态代码块中,经过 System.loadLibrary()
方法加载一个名为 hello_jni
的库,在 Linux
平台下,这个库的全名叫作 libhello_jni.so
,在 Windows
平台下,这个库的名字叫作 hello_jni.dll
。因为我使用的是 Ubuntu
,所以一会编译这个库的名字就 必须 为 libhello_jni.so
。c++
而后,定义了一个 native
方法 helloFromJNI()
,这个方法须要在动态库中实现,这个稍后就会看到。shell
最后,在 main()
方法中,调用这个 native
方法,并输出这个方法的返回结果。windows
在编译 Hello.java
文件以前,咱们须要建立一个存放字节码文件的目录,这个目录暂且就叫作 classes
吧oracle
bxll:~/JNIDemo$ mkdir classes
复制代码
而后,咱们把编译生成的字节码文件输出到这个目录jvm
bxll:~/JNIDemo$ javac -d classes/ Hello.java
复制代码
javac
的-d
参数表示输出的目录,更多参数请参考 javac 。ide
在执行生成头文件操做以前,咱们必需要搞清楚一个问题,那就是为什么要生成头文件? 由于头文件中声明的函数和Java
文件中声明的native
方法是一一对应的关系,虚拟机会自动帮咱们创建这层联系,这也称之为静态注册。
在生成头文件以前,咱们须要建立一个存放头文件的目录jni
bxll:~/JNIDemo$ mkdir jni
复制代码
而后把生成头文件的目录指定为 jni
bxll:~/JNIDemo$ javah -classpath classes/ -d jni/ com.bxll.jnidemo.Hello
复制代码
javah
的-classpath
参数指定字节码的目录,就是咱们刚才编译文件所指定的目录,-d
参数指定头文件生成的目录,最后的com.bxll.jnidemo.Hello
指定字节码文件的全路径。更多的javah
命令参数请参考 javah 。
ok, 如今头文件生成了,咱们如今来看看它的内容吧,简化版的内容以下
// com_bxll_jnidemo_Hello.h
/* * Class: com_bxll_jnidemo_Hello * Method: helloFromJNI * Signature: ()Ljava/lang/String; */
JNIEXPORT jstring JNICALL Java_com_bxll_jnidemo_Hello_helloFromJNI (JNIEnv *, jclass);
复制代码
咱们只须要关心这个函数原型(其余都是C/C++相关的事),由于Java_com_bxll_jnidemo_Hello_helloFromJNI
这个函数就是对应的 Hello.java
中的 native
方法 helloFromJNI()
。
另外呢,咱们还能够看到有三行注释,第一个注释 Class: com_bxll_jnidemo_Hello
指明了这个函数与哪一个 Java
类 相关,第二个注释 helloFromJNI
指明是实现了哪一个 native
方法,第三个参数 ()Ljava/lang/String;
表明Java
类的native
方法在JNI
中的签名。
JNIEXPORT
和JNICALL
都是宏,由于不一样平台调用动态库中的方法有不一样的规范,而这两个宏就是为了作兼容处理,在Linux
平台,这两个宏其实没有什么用,由于这两个宏都是定义为空的。
既然从头文件中已经了解到函数原型,那么就好实现了
// com_bxll_jnidemo_Hello.cpp
#include "com_bxll_jnidemo_Hello.h"
extern "C" JNIEXPORT jstring JNICALL Java_com_bxll_jnidemo_Hello_helloFromJNI (JNIEnv * env, jclass clazz) {
const char * str_hello = "Hello from C++";
return env->NewStringUTF(str_hello);
}
复制代码
这里涉及到 JNI
如何生成字符串的,这里暂不作详述。
既然咱们已经实现了底层函数,就须要将这些打包成库,以方便 Java
层加载。咱们选择把源文件打包成动态库,可是在执行这个动做以前,咱们必须保证操做系统的 Java
开发环境已经部署稳当,最好也设置了 JAVA_HOME
环境变量,先看下个人 JAVA_HOME
环境变量
bxll:~/JNIDemo$ echo $JAVA_HOME
/usr/lib/jvm/java-8-openjdk-amd64/
复制代码
那么,如今来生成动态库吧
bxll:~/JNIDemo$ g++ -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -fPIC -shared jni/com_bxll_jnidemo_Hello.cpp -o jni/libhello_jni.so
复制代码
g++
的-I
参数指明JNI
头文件的位置,-fPIC
表示变成成与位置无关的独立代码,-shared
表示编译成动态库,-o
指明生成动态库的目录以及名字,在Linux
系统下,动态库的名字的形式为libXXX.so
。
如今动态库都生成了,那么能够运行程序了吗?咱们试一下
bxll:~/JNIDemo$ java -classpath classes/ com.bxll.jnidemo.Hello
Exception in thread "main" java.lang.UnsatisfiedLinkError: no hello_jni in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
at java.lang.System.loadLibrary(System.java:1122)
at com.bxll.jnidemo.Hello.<clinit>(Hello.java:6)
复制代码
java.lang.UnsatisfiedLinkError
就是告诉你动态库没有连接上,而且后面有说明缘由no hello_jni in java.library.path
,告诉你 java.library.path
没有发现名为 hello_jni
的动态库,在 Linux
平台下,也就是没有发现 libhello_jni.so
库。
既然咱们已经知道缘由是在 java.library.path
属性所指定的目录下没有找到库,那么我能够把生成的库放到这个指定路径下,这样就能够了吧。没错,确实能够,可是这样未免太麻烦,在 Linux
平台下,能够把库的路径加入到 LD_LIBRARY_PATH
环境变量中,程序也会在这个路径下搜索库。
因为个人开发环境中暂时尚未定制本身的库,所以 LD_LIBRARY_PATH
这个环境变量为空,那么如今咱们设置下
bxll:~/JNIDemo$export LD_LIBRARY_PATH=./jni/
复制代码
咱们把库的搜索路径指定到了当前目录下的 jni
目录,由于刚才咱们把动态库输出到这个目录下。
那么,如今再运行这个 Java
程序,你就会看到想要的效果
bxll:~/JNIDemo$ java -classpath classes/ com.bxll.jnidemo.Hello
Hello from C++
复制代码
结果已经说明一切。
Linux平台下静态库和动态库