不使用IDE作一次JNI开发

一般,在开发 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.soc++

而后,定义了一个 native 方法 helloFromJNI(),这个方法须要在动态库中实现,这个稍后就会看到。shell

最后,在 main() 方法中,调用这个 native 方法,并输出这个方法的返回结果。windows

编译Java文件

在编译 Hello.java 文件以前,咱们须要建立一个存放字节码文件的目录,这个目录暂且就叫作 classesoracle

bxll:~/JNIDemo$ mkdir classes
复制代码

而后,咱们把编译生成的字节码文件输出到这个目录jvm

bxll:~/JNIDemo$ javac -d classes/ Hello.java
复制代码

javac-d 参数表示输出的目录,更多参数请参考 javacide

生成头文件

在执行生成头文件操做以前,咱们必需要搞清楚一个问题,那就是为什么要生成头文件? 由于头文件中声明的函数和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中的签名。

JNIEXPORTJNICALL 都是宏,由于不一样平台调用动态库中的方法有不一样的规范,而这两个宏就是为了作兼容处理,在 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平台下静态库和动态库

cs-fundamentals.com/c-programmi…

相关文章
相关标签/搜索