Linux環境下JNI簡單的say hello的例子

Linux環境下JNI簡單的say hello的例子

  參考文章:https://www.ibm.com/developerworks/cn/java/l-linux-jni/

  JNI是Java Native Interface的縮寫,JVM可以通過JNI去調用本地(Native)方法,目前做後端開發的還是挺少會去寫JNI的,因爲本身JNI就有一定的性能開銷。目前來說可能客戶端用的比較多,下面說下碰到的使用場景吧:

  • android一些計算密集型的代碼限於機能可能會通過JNI來去實現
  • 還有一些對安全要求較高的加密解密的代碼會放到so中去,畢竟java代碼反編譯比較容易,本身so文件反編譯就比較麻煩,又可以繼續通過加固來增加反編譯的難度
  • 一些跨平臺的遊戲引擎比如cocos是用c++開發的,編譯到安卓平臺需要通過JNI來實現

  下面通過一個簡單的say hello的例子來簡單瞭解一下jni

第一步實現一個Java類,Hello 它提供SayHello方法:

public class Hello {
    static {
        try {
            // 需要加載的動態鏈接庫
            System.loadLibrary("hello");
        } catch (UnsatisfiedLinkError e) {
            System.err.println("Cannot load hello library:\n" + e.toString());
        }   
    }   

    // 要使用的本地方法的聲明
    public native void SayHello(String str);
}

然後通過javac編譯生成Hello.class文件

第二步,生成本地動態鏈接庫

1、要爲Hello這個類生成Java本地接口的頭文件,JDK中爲我們提供了javah來去生產JNI的頭文件,不再需要我們再去寫頭文件通過如下命令:

%JAVA_HOME%/bin/javah Hello

之後可以看到當前目錄多了一個Hello.h文件,內容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Hello */

#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Hello
 * Method:    SayHello
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_Hello_SayHello
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

2、創建Hello.cpp文件實現Hello.h中聲明的函數,內容如下:

#include "Hello.h"
#include <stdio.h>

// Hello.h中函數聲明的實現
JNIEXPORT void JNICALL Java_Hello_SayHello
(JNIEnv* env, jobject arg, jstring instring) {
    // 從 instring字符串取得指向字符串 UTF 編碼指針
    const jbyte* str = (const jbyte*) env->GetStringUTFChars(instring, JNI_FALSE);
    printf("Hello, %s\n", str);
    // 通知虛擬機本地代碼不再需要通過 str 訪問 Java字符串。
    env->ReleaseStringUTFChars(instring, (const char*)str);
    return;
}

所有的 JNI 調用都使用了 JNIEnv * 類型的指針,習慣上在 CPP 文件中將這個變量定義爲 env,它是任意一個本地方法的第一個參數。env 指針指向一個函數指針表,在c++中可以直接用"->"操作符訪問其中的函數。

jobject 指向在此 Java 代碼中實例化的 Java 對象 LocalFunction 的一個句柄,相當於 this 指針。

後續的參數就是本地調用中有 Java 程序傳進的參數,本例中只有一個 String 型參數。 對於字符串型參數,因爲在本地代碼中不能直接讀取 Java 字符串,而必須將其轉換爲 C /C++ 字符串或 Unicode。
3、編譯生成動態鏈接庫
使用gcc編譯時,需要聲明jdk include的頭文件地址, 通過 -fPIC -c 來生成.o的目標文件

gcc -I/usr/lib/jvm/java/include -I/usr/lib/jvm/java/include/linux -fPIC -c Hello.cpp

生成 libhello.so文件(動態鏈接庫需要lib開頭),命令如下:

gcc -shared -o libhello.so Hello.o

設置環境變量,讓程序能找到so文件.

export LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH

4、編譯一個簡單的java代碼來測試我們實現的本地方法。

public class ToSay {

    public static void main(String[] args) {
        Hello hello = new Hello();
        hello.SayHello("張三");
    }   

}

使用javac編譯ToSay.java,生成ToSay.class
執行 java ToSay後我們會看到屏幕上出現 Hello, 張三

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章