原帖地址:http://blog.csdn.net/qiuxiaolong007/article/details/7860610
除了使用傳統方法實現JNI外,也可以使用RegisterNatives實現JNI。和傳統方法相比,使用RegisterNatives的好處有三點: 1、C++中函數命名自由,不必像javah自動生成的函數聲明那樣,拘泥特定的命名方式; 2、效率高。傳統方式下,Java類call本地函數時,通常是依靠VM去動態尋找.so中的本地函數(因此它們才需要特定規則的命名格式),而使用RegisterNatives將本地函數向VM進行登記,可以讓其更有效率的找到函數; 3、運行時動態調整本地函數與Java函數值之間的映射關係,只需要多次call RegisterNatives()方法,並傳入不同的映射表參數即可。 爲了使用RegisterNatives,我們需要了解JNI_OnLoad和JNI_OnUnload函數。JNI_OnLoad()函數在VM執行System.loadLibrary(xxx)函數時被調用,它有兩個重要的作用:
- 指定JNI版本:告訴VM該組件使用那一個JNI版本(若未提供JNI_OnLoad()函數,VM會默認該使用最老的JNI 1.1版),如果要使用新版本的JNI,例如JNI 1.4版,則必須由JNI_OnLoad()函數返回常量JNI_VERSION_1_4(該常量定義在jni.h中) 來告知VM。
- 初始化設定,當VM執行到System.loadLibrary()函數時,會立即先呼叫JNI_OnLoad()方法,因此在該方法中進行各種資源的初始化操作最爲恰當,RegisterNatives也在這裏進行。
JNI_OnUnload()當VM釋放該組件時被調用,JNI_OnUnload()函數的作用與JNI_OnLoad()對應,因此在該方法中進行善後清理,資源釋放的動作最爲合適。
Java代碼和使用哪種方式實現JNI無關,如下所示:
class MyJavaClass { public int iValue; public void Squa(){iValue = iValue*iValue;} } public class RegisterNativesTest { static{ System.load("/home/zmh/workspace/RegisterNativesTest/lib/libCallClass.so"); } public static void main(String[] args) { RegisterNativesTest app = new RegisterNativesTest(); MyJavaClass obj = new MyJavaClass(); obj.iValue = 10; System.out.println("Before callCustomClass: " + obj.iValue); app.callCustomClass(obj); System.out.println("After callCustomClass: " + obj.iValue); } private native void callCustomClass(MyJavaClass obj); }
C++的代碼可以分爲兩部分:實現callCustomClass方法和註冊callCustomClass。
實現callCustomClass方法的代碼如下:
void callCustomClass(JNIEnv* env, jobject, jobject obj) { jclass cls = env->GetObjectClass(obj); jfieldID fid = env->GetFieldID(cls, "iValue", "I"); jmethodID mid = env->GetMethodID(cls, "Squa", "()V"); int value = env->GetIntField(obj, fid); printf("Native: %d\n", value); env->SetIntField(obj, fid, 5); env->CallVoidMethod(obj, mid); value = env->GetIntField(obj, fid); printf("Native:%d\n", value); }
C++函數內忽略this指針,他所接收的jobject是“Java程序代碼傳遞過來的Java object reference“在原生端的形式,在C++中對jobject的改變在java中也是有效的。如果想要訪問Java數據成員和函數,得先使用GetFieldID或GetMethodID分別獲取數據成員和函數的識別碼,這兩個函數的參數依次爲1)class object;2)包含元素名稱的字符串;3)表示類型的字符串。
註冊callCustomClass在JNI_OnLoad中進行,代碼如下:
static JNINativeMethod s_methods[] = { {"callCustomClass", "(LMyJavaClass;)V", (void*)callCustomClass}, }; int JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { return JNI_ERR; } jclass cls = env->FindClass("LRegisterNativesTest;"); if (cls == NULL) { return JNI_ERR; } int len = sizeof(s_methods) / sizeof(s_methods[0]); if (env->RegisterNatives(cls, s_methods, len) < 0) { return JNI_ERR; } return JNI_VERSION_1_4; }
在C++和Java中創建關聯的是JNINativeMethod,它在jni.h中定義:
/* * used in RegisterNatives to describe native method name, signature, * and function pointer. */ typedef struct { char *name; char *signature; void *fnPtr; } JNINativeMethod;
name是java中定義的native函數的名字,fnPtr是函數指針,也就是C++中java native函數的實現。signature是java native函數的簽名,可以認爲是參數和返回值。比如(LMyJavaClass;)V,表示函數的參數是LMyJavaClass,返回值是void。對於基本類型,對應關係如下:
字符 Java類型 C/C++類型 V void void Z jboolean boolean I jint int J jlong long D jdouble double F jfloat float B jbyte byte C jchar char S jshort short
數組則以"["開始,用兩個字符表示,比如int數組表示爲[I,以此類推。
如果參數是Java類,則以"L"開頭,以";"結尾,中間是用"/"隔開包及類名,例如Ljava/lang/String;,而其對應的C++函數的參數爲jobject,一個例外是String類,它對應C++類型jstring。