實現JNI的另一種方法:使用RegisterNatives方法傳遞和使用Java自定義類 (轉)

原帖地址: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。

 

 

發佈了103 篇原創文章 · 獲贊 1 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章