不使用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…

相關文章
相關標籤/搜索