當我們在調用一個 Java native 方法的時候,方法中的參數是如何傳遞給 C/C++ 本地函數中的呢?Java 方法中的參數與 C/C++ 函數中的參數,它們之間是怎么轉換的呢?我猜你應該也有相關的疑慮吧,咱們先來看一個例子,還是以 HelloWorld 為例:
HelloWorld.java:
package com.study.jnilearn;
class MyClass {}
public class HelloWorld {
public static native void test(short s, int i, long l, float f, double d, char c,
boolean z, byte b, String str, Object obj, MyClass p, int[] arr);
public static void main(String[] args) {
String obj = "obj";
short s = 1;
long l = 20;
byte b = 127;
test(s, 1, l, 1.0f, 10.5, 'A', true, b, "中國", obj, new MyClass(), new int[] {});
}
static {
System.loadLibrary("HelloWorld");
}
}
在 HelloWorld.java 中定義了一個 test 的 native 方法,該方法中一個共有 12 個參數,其中前面 8 個為基本數據類型,后面 4 個全部為引用類型。
由 HelloWorld.class 生成的 native 函數原型及實現:
/*
* Class: com_study_jnilearn_HelloWorld
* Method: test
* Signature: (SIJFDCZBLjava/lang/String;Ljava/lang/Object;Lcom/study/jnilearn/MyClass;[I)V
*/
JNIEXPORT void JNICALL Java_com_study_jnilearn_HelloWorld_test
(JNIEnv *env, jclass cls, jshort s, jint i, jlong l, jfloat f,
jdouble d, jchar c, jboolean z, jbyte b, jstring j_str, jobject jobj1, jobject job2, jintArray j_int_arr)
{
printf("s=%hd, i=%d, l=%ld, f=%f, d=%lf, c=%c, z=%c, b=%d", s, i, l, f, d, c, z, b);
const char *c_str = NULL;
c_str = (*env)->GetStringUTFChars(env, j_str, NULL);
if (c_str == NULL)
{
return; // memory out
}
(*env)->ReleaseStringUTFChars(env, j_str, c_str);
printf("c_str: %s\n", (char*)c_str);
}
調用 test 方法的輸出結果:
從頭文件函數的原型可以得知,test 方法中形參的數據類型自動轉換成了 JNI 中相應的數據類型,不難理解,在調用 Java native 方法將實參傳遞給 C/C++ 函數的時候,會自動將 java 形參的數據類型自動轉換成 C/C++ 相應的數據類型,所以我們在寫 JNI 程序的時候,必須要明白它們之間數據類型的對應關系。
在 Java 語言中數據類型分為基本數據類型和引用類型,其中基本數據類型有 8 種:byte、char、short、int、long、float、double、boolean,除了基本數據類型外其它都是引用類型:Object、String、數組等。8 種基本數據類型分別對應JNI數據類型中的 jbyte、jchar、jshort、jint、jlong、jfloat、jdouble、jboolean。所有的 JNI 引用類型全部是 jobject 類型,為了使用方便和類型安全,JNI 定義了一個引用類型集合,集合當中的所有類型都是 jobject 的子類,這些子類和 Java 中常用的引用類型相對應。例如:jstring 表示字符串、jclass 表示 class 字節碼對象、jthrowable 表示異常、jarray 表示數組,另外 jarray 派生了 8 個子類,分別對應Java 中的 8 種基本數據類型(jintArray、jshortArray、jlongArray等)。下面再回顧頭來看看 test 方法與Java_com_study_jnilearn_HelloWorld_test
函數中參數類型的對應關系:
// HelloWorld.java
public static native void test(short s, int i, long l, float f, double d, char c,
boolean z, byte b, String str, Object obj, MyClass p);
// HelloWorld.h
JNIEXPORT void JNICALL Java_com_study_jnilearn_HelloWorld_test
(JNIEnv *, jclass, jshort, jint, jlong, jfloat, jdouble, jchar, jboolean, jbyte, jstring, jobject, jobject, jintArray);
從上面兩個函數的參數中可以看出來,除了 JNIEnv 和 jclass 這兩個參數外,其它參數都是一一對應的。下面是JNI 規范文檔中描述 Java 與 JN I數據類型的對應關系:
基本數據類型:
引用類型:
JNI 如果使用 C++ 語言編寫的話,所有引用類型派生自 jobject,使用 C++ 的繼承結構特性,使用相應的類型。如下所示:
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
JNI 如果使用 C 語言編寫的話,所有引用類型使用 jobject,其它引用類型使用 typedef 重新定義,如:typedef jobject jstring 。 jvalue 是一個 unio(聯合)類型,在 C 語中為了節約內存,會用聯合類型變量來存儲聲明在聯合體中的任意類型數據 。在 JNI 中將基本數據類型與引用類型定義在一個聯合類型中,表示用 jvalue 定義的變量,可以存儲任意 JNI 類型的數據,后面會介紹 jvalue 在 JNI 編程當中的應用。原型如下:
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
NdkDemo代碼已上傳至Github