JNI
是Java Native Interface
的縮寫,是Java
平台的本地調用,從Java1.1
就成為了Java
標准的一部分,它允許Java
代碼和其它語言的代碼進行互相調用,只要調用約定支持即可,尤其和C/C++
的互相調用。
雖然使用Java
與本地編譯的代碼進行交互,會喪失平台的可移植性,但是在特定情況下,這些問題是可以接受的,如:
1.使用一些舊的庫
2.需要操作系統交互
3.提高程序的性能
一、jni介紹
Java
是通過定義native
方法,然后用其它語言實現該方法,最后在Java
運行時,動態地加載該方法實現,通過調用native
的方法,進而實現Java
的本地調用。
1.實現架構
JVM
封裝了各種操作系統的差異性,提供了jni
技術,使得開發中可以通過Java
程序調用到操作系統的函數,進而與其它技術進行交互。下圖是Linux
平台jni
的調用流程。Java
應用程序通過jni
接口調用動態鏈接庫*.so
,來實現jni
的功能。
2.類型映射
Java基本數據類型與C語言基本數據類型的對應
3.常用方法簡介
1) GetStringUTFLength
以字節為單位返回字符串的UTF-8
長度
// jsize (JNICALL *GetStringUTFLength)(JNIEnv *env, jstring str)
int len = (*env)->GetStringUTFLength(env, str);
2) GetStringUTFChars
返回指向字符串的UTF-8
字符數組的指針。該數組在被ReleaseStringUTFChars()
釋放前將一直有效
// const char* (JNICALL *GetStringUTFChars)(JNIEnv *env, jstring str, jboolean *isCopy)
const char *buf = (*env)->GetStringUTFChars(env, str, NULL);
當 isCopy
為 JNI_FALSE
,不要修改返回值,不然將改變java.lang.String
的不可變語義。 一般會把isCopy
設為NULL
,不關心 Java VM
對返回的指針是否直接指向java.lang.String
的內容
3) ReleaseStringUTFChars
通知虛擬機平台相關代碼無需再訪問utf
,utf
參數是一個指針,可利用GetStringUTFChars()
獲得
// void (JNICALL *ReleaseStringUTFChars)(JNIEnv *env, jstring str, const char* chars)
(*env)->ReleaseStringUTFChars(env, str, buf);
4) NewStringUTF
利用UTF-8
字符數組構造新java.lang.String
對象
// jstring (JNICALL *NewStringUTF)(JNIEnv *env, const char *utf)
(*env)->NewStringUTF(env, "hello");
更多實用方法,請參考jni.h
二、jni實現步驟
下面介紹jni
的具體實現步驟,主要是通過Java
程序調用C
方法,跑通整兒jni
的調用流程。
1.編寫java類
編寫Java
的Hello
類,定義一個native
的本地方法
public class Hello {
public native static String sayHello(String name);
static {
System.load("你的*.so的絕對路徑");
}
public static void main(String[] args) {
Hello hello = new Hello();
String ret = hello.sayHello("kelvin");
System.out.println(ret);
}
}
2.編譯java類
使用javac
命令進行編譯
# javac Hello.java
3.生成本地文件*.h
這是關鍵的一步,主要是生成本地方法簽名,依賴的是上一步的class
文件,
# javah -jni Hello
如果你的java
源文件有包名,在生成*.sh
的時候,也要帶包名轉化的路徑,即用classpath
指定包所在的路徑,不然在最后調用時,會報錯:UnsatisfiledLinkError
// java源文件包名
package kelvin.Java.dynamicso;
// 編譯時指定classpath
# javah -classpath /Users/kelvin/Documents -jni kelvin.Java.dynamicso.Hello
4.編寫本地方法
在生成的Hello.h
頭文件中,有需要實現的本地方法名,在實現時,要記得指定參數名稱
#include <stdio.h>
#include "Hello.h"
JNIEXPORT jstring JNICALL Java_Hello_sayHello(JNIEnv *env, jclass jc, jstring name)
{
const char *buf;
buf = (*env)->GetStringUTFChars(env, name, NULL);
if (NULL == buf)
{
return NULL;
}
printf("%s\n", buf);
(*env)->ReleaseStringUTFChars(env, name, buf);
return (*env)->NewStringUTF(env, "hello");
}
5.制作動態庫
由於是Linux
平台,需要制作后綴是.so
的動態庫,其中,需要指定jni.h
的路徑,必要時還需要jni_md.h
的路徑,該文件在jdk
的目錄中
# gcc -c -fPIC -I/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/include Hello.c -o Hello.o
# gcc -shared Hello.o -o libhello.so
6.調用動態庫
加載動態庫有2種方式:
1)load():需要指定庫的絕對路徑
2)loadLibrary():需要指定庫的相對路徑,即java.lib.path
現在jni
調用的一切都准備好了,進行最后的調用,有正常的打印輸出,表明jni
正常調用了
# java Hello
kelvin
hello
以上就是Linux
平台的jni
調用方式,下一篇介紹Windows
平台的jni
調用方式。。。
參考資料
JNI之String類型
Jni編程(三)c/c++ 獲取java字符串,以及java 獲取c/c++創建的對象
一天掌握Android JNI本地編程 快速入門