JNI的介面函式和指標
native程式碼想要訪問 java虛擬機器需要呼叫JNI方法,而獲取JNI方法則通過 JNI interface Pointer。它實際指向的就是一個都是指標的陣列,每個指標指向的都是一個介面函式
這樣做的優勢:
- JNI 命名和native code書寫分開,避免硬編碼
JNI interface Pointer 只在當前執行緒有效,即native 方法不能線上程之間傳遞(不同執行緒的指標可能不一 樣),VM保證同一個執行緒中呼叫多次JNI interface Pointer是同一個
編譯
JAVA VM支援多執行緒,native 方法在編譯的時候需要加上對應的引數,如 gcc加上 -D_REENTRANT或者-D_POSIX_C_SOURCE
載入
程式碼如下
package pkg;
class Cls {
native double f(int i, String s);
static {
System.loadLibrary(“pkg_Cls”); //名字可以隨便定義
}
}
複製程式碼
對於不同的系統,打包的字尾名會有不同,solaris系統一般是libpkg_Cls.so(使用的時候則是直接用的pkg_Cls)Win32的系統則是pkg_Cls.dll
連線
如果當前系統不支援動態連線,所有的Native方法必須預先和VM建立連線,通過System.loadLibrary是無法自動載入。如果要靜態連線可以使用 JNI的函式 RegisterNatives
靜態連線需要把所有的library複製到可執行的映像中;動態連線是把共享的library的名字放在一個可執行的映像中,當映像執行的時候才去連線
Native方法名
- 生成規則:Java_ 作為字首,類的全路徑名,用 “_” 分隔每一個目錄名,再加上 方法名,如果是過載的方法,則會新增 “__”和 方法簽名,比如: 全路徑是:com.study.jnilearn.HelloWorld,生成的方法是 Java_com_study_jnilearn_HelloWorld_sayHello:
- 查詢規則:VM查詢native library裡面的方法名,首先查詢短的名字,即方法名沒有引數簽名;然後查詢有引數簽名的方法;長方法名只有在native方法過載了另一個native方法的時候需要
方法簽名
方法簽名的格式為:(形參引數型別列表)返回值
。
形參引數列表中,引用型別以L開頭,後面緊跟類的全路徑名(需將.全部替換成/),以分號結尾
比如:long f(int n,String s,int[] arr); 對應的Native方法簽名是 (ILjava/lang/String;[I)J. 各種型別簽名對比
Native的方法引數
第一個引數是JNI Interface pointer(型別是 JNIEnv),如果是靜態native方法,第二個引數則是對應java class的引用,非靜態的native則對應的是 物件的引用,其它的引數對應的是java方法的引數
JNI的Hello world實現
- 建立自己的Hello world檔案,在其中使用Native方法
public class HelloWorld {
public static native String sayHello(String name);
public static void main(String[] args) {
String text = sayHello("paxi");
System.out.println(text);
}
static{
System.loadLibrary("HelloWorld");
}
}
複製程式碼
- 用javac編譯HelloWorld.java檔案
- 用javah編譯產生標頭檔案 HelloWorld.h
命令為javah -jni -d ./jni HelloWorld
;-d:將生成的檔案放到jni目錄下 生成結果如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: sayHello
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_HelloWorld_sayHello
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif
複製程式碼
- 用C實現HelloWorld.h中的函式
HelloWorld.c:
#include "HelloWorld.h"
#ifdef __cplusplus
extern "C"
{
#endif
/*
* Class: HelloWorld
* Method: sayHello
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_HelloWorld_sayHello
(JNIEnv *env, jclass cls, jstring j_str){
const char *c_str = NULL;
char buff[128] = {0};
c_str = (*env)->GetStringUTFChars(env,j_str,NULL);
if (c_str == NULL)
{
printf("out of memory\n");
return NULL;
}
printf("Java Str:%s\n", c_str);
sprintf(buff,"hello %s",c_str);
(*env)->ReleaseStringUTFChars(env,j_str,c_str);
return (*env)->NewStringUTF(env,buff);
}
#ifdef __cplusplus
}
#endif
複製程式碼
- 編譯C的程式碼生成native檔案
mac下命令為
gcc -dynamiclib -o extensions/libHelloWorld.jnilib jni/HelloWorld.c -framework JavaVM -I/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/include/darwin
* -dynamiclib:表示生成動態連結庫
* -o:指定動態連結庫編譯後生成的路徑以及檔名
* -framwork JavaVM -I:編譯JNI需要用到的JVM標頭檔案(jni.h)
複製程式碼
- 執行java程式,指定動態連結庫
命令為java -Djava.library.path=動態連結的目錄 Helloworld
java Str:paxi
hello paxi
複製程式碼