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, 張三