Java Native Interface(JNI)從零開始詳細教程

====================================================================================
首先聲明:這邊文章是我翻譯的文章(看了很多關於JNI的介紹,只有這篇個人認爲最好,因此忍不住想要翻譯給國內的各位),請勿隨意轉載,尊重文章原作者。
文章原始鏈接:https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html
這是南洋理工大學的在線學習note,有興趣的同學可以直接看原文。如果你對E文比較頭疼的話,可以直接看下面我翻譯的,我會盡量使用平實的語言去翻譯這篇文章,另外本人水平有限,如有翻譯不當之處還請各位賜教。===================================================================================

引言

有的時候我們需要使用本地代碼(C/C++)來克服Java中的內存管理和性能問題,Java通過JNI機制來支持內地代碼的使用。
想要比較好地理解JNI是比較難的,因爲它包含了兩種語言和運行時機制。
在繼續之前,我應該假設你具備以下知識點和技能:
1. java
2. C/C++和gcc編譯器
3. 對於windows而言,熟悉Gygwin或者MinGW
4. 對於IDE而言,熟悉Eclipse C/C++ Development Tool (CDT)

開始

使用C來實現JNI

步驟1,編寫一個使用C實現函數的java類,HelloJNI.java:

public class HelloJNI {
   static {
      System.loadLibrary("hello"); // Load native library at runtime
                                   // hello.dll (Windows) or libhello.so (Unixes)
   }

   // Declare a native method sayHello() that receives nothing and returns void
   private native void sayHello();

   // Test Driver
   public static void main(String[] args) {
      new HelloJNI().sayHello();  // invoke the native method
   }
}

上面代碼的靜態代碼塊在這個類被類加載器加載的時候調用了System.loadLibrary()方法來加載一個native庫“hello”(這個庫中實現了sayHello函數)。這個庫在windows品臺上對應了“hello.dll”,而在類UNIX平臺上對應了“libhello.so”。這個庫應該包含在Java的庫路徑(使用java.library.path系統變量表示)上,否則這個上面的程序會拋出UnsatisfiedLinkError錯誤。你應該使用VM的參數-Djava.library.path=path_to_lib來指定包含native庫的路徑。
接下來,我們使用native關鍵字將sayHello()方法聲明爲本地實例方法,這就很明顯地告訴JVM:這個方法實現在另外一個語言中(C/C++),請去那裏尋找他的實現。注意,一個native方法不包含方法體,只有聲明。上面代碼中的main方法實例化了一個HelloJJNI類的實例,然後調用了本地方法sayHello()。
下面,我們編譯HelloJNI.java成HelloJNI.class

javac HelloJNI.java

接下來,我們利用上面生成的class文件生成用於編寫C/C++代碼的頭文件,使用jdk中的javah工具完成:

javah HelloJNI

上面的命令執行完之後生成了HelloJNI.h:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJNI
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

我們看到,上面的頭文件中生成了一個Java_HelloJNI_sayHello的C函數:

JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

將java的native方法轉換成C函數聲明的規則是這樣的:Java_{package_and_classname}_{function_name}(JNI arguments)。包名中的點換成單下劃線。需要說明的是生成函數中的兩個參數:
1. JNIEnv *:這是一個指向JNI運行環境的指針,後面我們會看到,我們通過這個指針訪問JNI函數
2. jobject:這裏指代java中的this對象
下面我們給出的例子中沒有使用上面的兩個參數,不過後面我們的例子會使用的。到目前爲止,你可以先忽略JNIEXPORT和JNICALL這兩個玩意。
上面頭文件中有一個extern “C”,同時上面還有C++的條件編譯語句,這麼一來大家就明白了,這裏的函數聲明是要告訴C++編譯器:這個函數是C函數,請使用C函數的簽名協議規則去編譯!因爲我們知道C++的函數簽名協議規則和C的是不一樣的,因爲C++支持重寫和重載等面向對象的函數語法。
接下來,我們給出C語言的實現,以實現上面的函數:
C語言實現:

#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h"

// Implementation of native method sayHello() of HelloJNI class
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
   printf("Hello World!\n");
   return;
}

將上面的代碼保存爲HelloJNI.c。jni.h頭文件在 “\include” 和 “\include\win32”目錄下,這裏的JAVA_HOME是指你的JDK安裝目錄。
這段C代碼的作用很簡單,就是在終端上打印Hello Word!這句話。
下面我們編譯這段代碼,使用GCC編譯器:
對於windows上的MinGW:

> set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_{xx}
      // Define and Set environment variable JAVA_HOME to JDK installed directory
      // I recommend that you set JAVA_HOME permanently, via "Control Panel""System""Environment Variables"
> echo %JAVA_HOME%
      // In Windows, you can refer a environment variable by adding % prefix and suffix 
> gcc -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o hello.dll HelloJNI.c
      // Compile HellJNI.c into shared library hello.dll

也可以分步編譯:

// Compile-only with -c flag. Output is HElloJNI.o
> gcc -c -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" HelloJNI.c

// Link into shared library "hello.dll"
> gcc -Wl,--add-stdcall-alias -shared -o hello.dll HelloJNI.o

下面,我們使用nm命令來查看生成hello.dll中的函數:

> nm hello.dll | grep say
624011d8 T _Java_HelloJNI_sayHello@8

對於windows上的Cygwin:
首先,你需要講__int64定義成“long long”類型,通過-D _int64=”long long選項實現。
對於gcc-3,請包含選項-nmo -cygwin來編譯dll庫,這些庫是不依賴於Cygwin dll的。

> gcc-3 -D __int64="long long" -mno-cygwin -Wl,--add-stdcall-alias 
  -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -shared -o hello.dll HelloJNI.c

對於gcc-4,我目前還沒有找到正確的編譯選項。
==================================這部分是筆者添加的==================================
原文中只給出了windows平臺上編譯方法,下面我給出Linux等類UNIX上編譯方法:

gcc -fPIC --shared HelloJNI.c -o libhello.so -I /usr/lib/jvm/java-7-openjdk-amd64/include/

上面的命令編譯生成一個libhello.so共享庫在當前目錄下
==================================這部分是筆者添加的==================================
接下來,讓我們運行一下上面的代碼吧:

> java HelloJNI
or
> java -Djava.library.path=. HelloJNI

有的時候,你可能需要使用-Djava.library.path來指定加載庫的位置,因爲可能報出java.lang.UnsatisfiedLinkError錯誤.
==================================這部分是筆者添加的==================================
我們首先使用nm命令(關於nm請自行Google或者man)查看libhello.so中都有那些函數:
這裏寫圖片描述
可以看到我們的sayHello函數已經在這個裏面,這說明我們編譯的基本沒有問題。
下面,我給出在我電腦上運行的效果(原文作者沒有給出):
首先我們執行java HelloJNI,看看能不能運行:
這裏寫圖片描述
果然,出現了UnsatisfiedLinkError錯誤,原因是VM去標準路徑下查找這個庫,發現找不到,然後就掛了。因此我們還是需要使用-Djava.library.path來明確告訴VM我們的庫在哪裏(當然,你也可以將你編譯出來的庫放到系統標準路徑中,比如/usr/lib目錄下):
這裏寫圖片描述
現在OK了,因爲我們明確告訴VM,我們的libhello.so就在當前目錄下,不用傻傻地去系統中找啦!!
==================================這部分是筆者添加的==================================

使用C/C++混合實現JNI

第一步:編寫一個使用本地代碼的java類:HelloJNICpp.java

public class HelloJNICpp {
   static {
      System.loadLibrary("hello"); // hello.dll (Windows) or libhello.so (Unixes)
   }

   // Native method declaration
   private native void sayHello();

   // Test Driver
   public static void main(String[] args) {
      new HelloJNICpp().sayHello();  // Invoke native method
   }
}

同樣地,我們使用javac來編譯這個代碼:

> javac HelloJNICpp.java

步驟2:生成C/C++的頭文件

> javah HelloJNICpp

上面命令會生成一個HelloJNICpp.h的文件,並且這個文件中聲明瞭這個本地函數:

JNIEXPORT void JNICALL Java_HelloJNICpp_sayHello(JNIEnv *, jobject);

步驟3:C/C++編碼實現,HelloJNICppImpl.h, HelloJNICppImpl.cpp, 和 HelloJNICpp.c
這裏,我們使用C++來實現真正的函數(”HelloJNICppImpl.h” 和 “HelloJNICppImpl.cpp”),而使用C來和java進行交互。(譯者注:這樣就可以把JNI的代碼邏輯和我們真正的業務邏輯分離開了!)
C++頭文件:”HelloJNICppImpl.h”

#ifndef _HELLO_JNI_CPP_IMPL_H
#define _HELLO_JNI_CPP_IMPL_H

#ifdef __cplusplus
        extern "C" {
#endif
        void sayHello ();
#ifdef __cplusplus
        }
#endif

#endif

C++的代碼實現:”HelloJNICppImpl.cpp”

#include "HelloJNICppImpl.h"
#include  <iostream>

using namespace std;

void sayHello () {
    cout << "Hello World from C++!" << endl;
    return;
}

C代碼實現和Java的交互:”HelloJNICpp.c”

#include <jni.h>
#include "HelloJNICpp.h"
#include "HelloJNICppImpl.h"

JNIEXPORT void JNICALL Java_HelloJNICpp_sayHello (JNIEnv *env, jobject thisObj) {
    sayHello();  // invoke C++ function
    return;
}

講上面的代碼編譯成一個共享庫(在windows上是hello.dll)。
使用windows上的MinGW GCC:

> set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_{xx}
> g++ -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" 
      -shared -o hello.dll HelloJNICpp.c HelloJNICppImpl.cpp

步驟4:運行java代碼

> java HelloJNICpp
or
> java -Djava.library.path=. HelloJNICpp

java package中的JNI

在真正的產品化中,所有的java類都是有自己的包的,而不是一個默認的沒有名字的包。下面我們說明一下java中的package怎麼在JNI中使用。
步驟1:使用JNI的程序, myjni\HelloJNI.java

package myjni; // 多了包名定義

public class HelloJNI {
   static {
      System.loadLibrary("hello"); // hello.dll (Windows) or libhello.so (Unixes)
   }
   // A native method that receives nothing and returns void
   private native void sayHello();

   public static void main(String[] args) {
      new HelloJNI().sayHello();  // invoke the native method
   }
}

上面的這個類應該放在myjni目錄下。然後我們編譯這個代碼:

// change directory to package base directory
> javac myjni\HelloJNI.java

步驟2:生成C/C++頭文件
如果你的java代碼是放在一個包中的,那麼你需要使用完全限定名稱來生成C/C++頭文件的。你可能會需要使用-classpath選項來指定JNI程序的classpath路徑,並且可能會使用-d選項來指定生成頭文件的目標文件夾。

> javah --help
......

// Change directory to package base directory
> javah -d include myini.HelloJNI

在上面的例子中,我們選擇將生層的頭文件放在include目錄下,因此,我們輸出的就是:”include\myjni_HelloJNI.h”.這個頭文件聲明瞭這樣的本地函數:

JNIEXPORT void JNICALL Java_myjni_HelloJNI_sayHello(JNIEnv *, jobject);

我們看到,和上面的例子相比,這裏的名字規則是這樣的:Java__methodName,同時,點號換成單下劃線。
步驟3:C代碼實現:HelloJNI.c

#include <jni.h>
#include <stdio.h>
#include "include\myjni_HelloJNI.h"

JNIEXPORT void JNICALL Java_myjni_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
   printf("Hello World!\n");
   return;
}

編譯C代碼:

> gcc -Wl,--add-stdcall-alias -I<JAVA_HOME>\include -I<JAVA_HOME>\include\win32 -shared -o hello.dll HelloJNI.c

運行代碼:

> java myjni.HelloJNI

在Eclipse中開發JNI

這部分作者寫的非常易懂,由於個人時間問題,就不翻譯了,大家看看就明白(其實就是一些Eclipse的設置問題,沒有什麼複雜的),或者百度看國內的也行。(抱歉了!)
原文鏈接:https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html

JNI基礎知識

上面我們簡單演示了怎麼使用JNI,現在我們來系統梳理一下JNI中涉及的基本知識。
JNI定義了以下數據類型,這些類型和Java中的數據類型是一致的:
1. Java原始類型:jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean這些分別對應這java的int, byte, short, long, float, double, char and boolean。
2. Java引用類型:jobject用來指代java.lang.Object,除此之外,還定義了以下子類型:
a. jclass for java.lang.Class.
b. jstring for java.lang.String.
c. jthrowable for java.lang.Throwable.
d. jarray對java的array。java的array是一個指向8個基本類型array的引用類型。於是,JNI中就有8個基本類型的array:jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray 和 jbooleanArray,還有一個就是指向Object的jobjectarray。
Native函數會接受上面類型的參數,並且也會返回上面類型的返回值。然而,本地函數(C/C++)是需要按照它們自己的方式處理類型的(比如C中的string,就是char *)。因此,需要在JNI類型和本地類型之間進行轉換。通常來講,本地函數需要:
1. 加收JNI類型的參數(從java代碼中傳來)
2. 對於JNI類型參數,需要講這些數據轉換或者拷貝成本地數據類型,比如講jstring轉成char *, jintArray轉成C的int[]。需要注意的是,原始的JNI類型,諸如jint,jdouble之類的不用進行轉換,可以直接使用,參與計算。
3. 進行數據操作,以本地的方式
4. 創建一個JNI的返回類型,然後講結果數據拷貝到這個JNI數據中
5. returnJNI類型數據
這其中最麻煩的事莫過於在JNI類型(如jstring, jobject, jintArray, jobjectArray)和本地類型(如C-string, int[])之間進行轉換這件事情了。不過所幸的是,JNI環境已經爲我們定義了很多的接口函數來做這種煩人的轉換。(譯者注:這裏就需要使用上面我們提到的JNIEnv*那個參數了!)

在Java和Native代碼之間傳遞參數和返回值

傳遞基本類型

傳遞java的基本類型是非常簡單而直接的,一個jxxx之類的類型已經定義在本地系統中了,比如:jint, jbyte, jshort, jlong, jfloat, jdouble, jchar 和 jboolean分別對應java的int, byte, short, long, float, double, char 和 boolean基本類型。
Java JNI 程序:TestJNIPrimitive.java

public class TestJNIPrimitive {
   static {
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }

   // Declare a native method average() that receives two ints and return a double containing the average
   private native double average(int n1, int n2);

   // Test Driver
   public static void main(String args[]) {
      System.out.println("In Java, the average is " + new TestJNIPrimitive().average(3, 2));
   }
}

這個JNI程序加載了myjni.dll(windows)庫或者libmyjni.so(類UNIX)庫。並且聲明瞭一個native方法,這個方法接受兩個int類型的參數,並且返回一個double類型的返回值,這個值是兩個int型數的平均值。mian方法調用了average函數。
下面,我們將上面的java代碼編譯成TestJNIPrimitive.class,進而生成C/C++頭文件TestJNIPrimitive.h:

> javac TestJNIPrimitive.java
> javah TestJNIPrimitive       // Output is TestJNIPrimitive.h

C實現:TestJNIPrimitive.c

頭文件TestJNIPrimitive.h中包含了一個函數聲明:

JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average(JNIEnv *, jobject, jint, jint);

可以看到,這裏的jint和jdouble分別表示java中的int和double。
jni.h(windows上是win32/jni_mh.h)頭文件包含了這些數據類型的定義,同時多了一個jsize的定義:

// In "win\jni_mh.h" - machine header which is machine dependent
typedef long            jint;
typedef __int64         jlong;
typedef signed char     jbyte;

// In "jni.h"
typedef unsigned char   jboolean;
typedef unsigned short  jchar;
typedef short           jshort;
typedef float           jfloat;
typedef double          jdouble;
typedef jint            jsize;

有趣的是,jint對應到C的long類型(至少是32bit的),而不是C的int類型(至少是16bit的)。於是,在C代碼中要使用jint而不是int是很重要的。同時,CygWin不支持__int64類型。
TestJNIPrimitive.c的實現如下:

#include <jni.h>
#include <stdio.h>
#include "TestJNIPrimitive.h"

JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
          (JNIEnv *env, jobject thisObj, jint n1, jint n2) {
   jdouble result;
   printf("In C, the numbers are %d and %d\n", n1, n2);
   result = ((jdouble)n1 + n2) / 2.0;
   // jint is mapped to int, jdouble is mapped to double
   return result;
}

然後,我們編譯代碼成一個共享庫:

// MinGW GCC under Windows
> set JAVA_HOME={jdk-installed-directory}
> gcc -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o myjni.dll TestJNIPrimitive.c

最後,我們運行這個java代碼:

> java TestJNIPrimitive

C++實現 TestJNIPrimitive.cpp

代碼如下:

#include <jni.h>
#include <iostream>
#include "TestJNIPrimitive.h"
using namespace std;

JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
          (JNIEnv *env, jobject obj, jint n1, jint n2) {
   jdouble result;
   cout << "In C++, the numbers are " << n1 << " and " << n2 << endl;
   result = ((jdouble)n1 + n2) / 2.0;
   // jint is mapped to int, jdouble is mapped to double
   return result;
}

使用g++來編譯上面的代碼:

// MinGW GCC under Windows
> g++ -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o myjni.dll TestJNIPrimitive.cpp

傳遞字符串

Java JNI 程序:TestJNIString.java

public class TestJNIString {
   static {
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }
   // Native method that receives a Java String and return a Java String
   private native String sayHello(String msg);

   public static void main(String args[]) {
      String result = new TestJNIString().sayHello("Hello from Java");
      System.out.println("In Java, the returned string is: " + result);
   }
}

上面的代碼聲明瞭一個native函數sayHello,這個函數接受一個java的String,然後返回一個Java string,main方法調用了sayHello函數。
然後,我們編譯上面的代碼,並且生成C/C++的頭文件:

> javac TestJNIString.java
> javah TestJNIString

C代碼實現:TestJNIString.c

上面的頭文件TestJNIString.h聲明瞭這樣的一個函數:

JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *, jobject, jstring);

JNI定義了jstring類型應對java的String類型。上面聲明中的最後一個參數jstring就是來自Java代碼中的String參數,同時,返回值也是一個jstring類型。
傳遞一個字符串比傳遞基本類型要複雜的多,因爲java的String是一個對象,而C的string是一個NULL結尾的char數組。因此,我們需要將Java的String對象轉換成C的字符串表示形式:char *。
前面我們提到,JNI環境指針JNIEnv *已經爲我們定義了非常豐富的接口函數用來處理數據的轉換:
1. 調用const char* GetStringUTFChars(JNIEnv*, jstring, jboolean*)來將JNI的jstring轉換成C的char *
2. 調用jstring NewStringUTF(JNIEnv*, char*)來將C的char *轉換成JNI的jstring
因此我們的C程序基本過程如下:
1. 使用GetStringUTFChars()函數來將jstring轉換成char *
2. 然後進行需要的數據處理
3. 使用NewStringUTF()函數來將char *轉換成jstring,並且返回

#include <jni.h>
#include <stdio.h>
#include "TestJNIString.h"

JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *env, jobject thisObj, jstring inJNIStr) {
   // Step 1: Convert the JNI String (jstring) into C-String (char*)
   const char *inCStr = (*env)->GetStringUTFChars(env, inJNIStr, NULL);
   if (NULL == inCSt) return NULL;

   // Step 2: Perform its intended operations
   printf("In C, the received string is: %s\n", inCStr);
   (*env)->ReleaseStringUTFChars(env, inJNIStr, inCStr);  // release resources

   // Prompt user for a C-string
   char outCStr[128];
   printf("Enter a String: ");
   scanf("%s", outCStr);    // not more than 127 characters

   // Step 3: Convert the C-string (char*) into JNI String (jstring) and return
   return (*env)->NewStringUTF(env, outCStr);
}

將上面的代碼編譯成共享庫:

// MinGW GCC under Windows
> gcc -Wl,--add-stdcall-alias -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -shared -o myjni.dll TestJNIString.c

最後,運行代碼:

> java TestJNIString
In C, the received string is: Hello from Java
Enter a String: test
In Java, the returned string is: test

JNI中的string轉換函數

上面我們展示了兩個函數,現在我們全面梳理下JNI爲我們提供的函數。JNI支持Unicode(16bit字符)和UTF-8(使用1~3字節的編碼)轉化。一般而言,我們應該在C/C++中使用UTF-8的編碼方式。
JNI系統提供瞭如下關於字符串處理的函數(一共兩組,UTF8和Unicode):

// UTF-8 String (encoded to 1-3 byte, backward compatible with 7-bit ASCII)
// Can be mapped to null-terminated char-array C-string
const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
   // Returns a pointer to an array of bytes representing the string in modified UTF-8 encoding.
void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
   // Informs the VM that the native code no longer needs access to utf.
jstring NewStringUTF(JNIEnv *env, const char *bytes);
   // Constructs a new java.lang.String object from an array of characters in modified UTF-8 encoding.
jsize GetStringUTFLength(JNIEnv *env, jstring string);
   // Returns the length in bytes of the modified UTF-8 representation of a string.
void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize length, char *buf);
   // Translates len number of Unicode characters beginning at offset start into modified UTF-8 encoding 
   // and place the result in the given buffer buf.

// Unicode Strings (16-bit character)
const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);
   // Returns a pointer to the array of Unicode characters
void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);
   // Informs the VM that the native code no longer needs access to chars.
jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize length);
   // Constructs a new java.lang.String object from an array of Unicode characters.
jsize GetStringLength(JNIEnv *env, jstring string);
   // Returns the length (the count of Unicode characters) of a Java string.
void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize length, jchar *buf);
   // Copies len number of Unicode characters beginning at offset start to the given buffer buf

GetStringUTFChars()函數可以將jstring轉成char *,這個函數會返回NULL,如果系統的內容分配失敗的話。因此,好的做法是檢查這個函數的返回是不是NULL。第三個參數是isCopy,這個參數是一個in-out參數,傳進去的是一個指針,函數結束的時候指針的內容會被修改。如果內容是JNI_TRUE的話,那麼代表返回的數據是jstring數據的一個拷貝,反之,如果是JNI_FALSE的話,就說明返回的字符串就是直接指向那個String對象實例的。在這種情況下,本地代碼不應該隨意修改string中的內容,因爲修改會代碼Java中的修改。JNI系統會盡量保證返回的是直接引用,如果不能的話,那就返回一個拷貝。通常,我們很少關心修改這些string ,因此我們這裏一般傳遞NULL給isCopy參數。
必須要注意的是,當你不在需要GetStringUTFChars返回的字符串的時候,一定記得調用ReleaseStringUTFChars()函數來將內存資源釋放!否則會內存泄露!並且上層java中的GC也不能進行!
另外,在GetStringUTFChars和ReleaseStringUTFChars不能block!
NewStringUTF()函數可以從char *字符串得到jstring。
關於更詳細的描述,請參考Java Native Interface Specification:http://docs.oracle.com/javase/7/docs/technotes/guides/jni/index.html

C++實現:TestJNIString.cpp

#include <jni.h>
#include <iostream>
#include <string>
#include "TestJNIString.h"
using namespace std;

JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *env, jobject thisObj, jstring inJNIStr) {
   // Step 1: Convert the JNI String (jstring) into C-String (char*)
   const char *inCStr = env->GetStringUTFChars(inJNIStr, NULL);
   if (NULL == inCStr) return NULL;

   // Step 2: Perform its intended operations
   cout << "In C++, the received string is: " << inCStr << endl;
   env->ReleaseStringUTFChars(inJNIStr, inCStr);  // release resources

   // Prompt user for a C++ string
   string outCppStr;
   cout << "Enter a String: ";
   cin >> outCppStr;

   // Step 3: Convert the C++ string to C-string, then to JNI String (jstring) and return
   return env->NewStringUTF(outCppStr.c_str());
}

使用g++編譯上面的代碼:

// MinGW GCC under Windows
> g++ -Wl,--add-stdcall-alias -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -shared -o myjni.dll TestJNIString.cpp

需要注意的是,在C++中,本地string類的函數調用語法不一樣。在C++中,我們使用env->來調用,而不是(env*)->。同時,在C++函數中不需要JNIEnv*這個參數了。

傳遞基本類型的數組

JNI 代碼:TestJNIPrimitiveArray.java

public class TestJNIPrimitiveArray {
   static {
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }

   // Declare a native method sumAndAverage() that receives an int[] and
   //  return a double[2] array with [0] as sum and [1] as average
   private native double[] sumAndAverage(int[] numbers);

   // Test Driver
   public static void main(String args[]) {
      int[] numbers = {22, 33, 33};
      double[] results = new TestJNIPrimitiveArray().sumAndAverage(numbers);
      System.out.println("In Java, the sum is " + results[0]);
      System.out.println("In Java, the average is " + results[1]);
   }
}

C語言實現:TestJNIPrimitiveArray.c

頭文件TestJNIPrimitiveArray.h包含以下函數聲明:

JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_average (JNIEnv *, jobject, jintArray);

在Java中,array是指一種類型,類似於類。一共有9種java的array,8個基本類型的array和一個object的array。JNI針對java的基本類型都定義了相應的array:jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray,並且也有面向object的jobjectArray。
同樣地,你需要在JNI array和Native array之間進行轉換,JNI系統已經爲我們提供了一系列的接口函數:
1. 使用jint* GetIntArrayElements(JNIEnv *env, jintArray a, jboolean *iscopy)將jintarray轉換成C的jint[]
2. 使用jintArray NewIntArray(JNIEnv *env, jsize len)函數來分配一個len字節大小的空間,然後再使用void SetIntArrayRegion(JNIEnv *env, jintArray a, jsize start, jsize len, const jint *buf)函數講jint[]中的數據拷貝到jintArray中去。
一共有8對類似上面的函數,分別對應java的8個基本數據類型。
因此,native程序需要:
1. 接受來自java的JNI array,然後轉換成本地array
2. 進行需要的數據操作
3. 將需要返回的數據轉換成jni的array,然後返回
下面是C代碼實現的TestJNIPrimitiveArray.c:

#include <jni.h>
#include <stdio.h>
#include "TestJNIPrimitiveArray.h"

JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_sumAndAverage
          (JNIEnv *env, jobject thisObj, jintArray inJNIArray) {
   // Step 1: Convert the incoming JNI jintarray to C's jint[]
   jint *inCArray = (*env)->GetIntArrayElements(env, inJNIArray, NULL);
   if (NULL == inCArray) return NULL;
   jsize length = (*env)->GetArrayLength(env, inJNIArray);

   // Step 2: Perform its intended operations
   jint sum = 0;
   int i;
   for (i = 0; i < length; i++) {
      sum += inCArray[i];
   }
   jdouble average = (jdouble)sum / length;
   (*env)->ReleaseIntArrayElements(env, inJNIArray, inCArray, 0); // release resources

   jdouble outCArray[] = {sum, average};

   // Step 3: Convert the C's Native jdouble[] to JNI jdoublearray, and return
   jdoubleArray outJNIArray = (*env)->NewDoubleArray(env, 2);  // allocate
   if (NULL == outJNIArray) return NULL;
   (*env)->SetDoubleArrayRegion(env, outJNIArray, 0 , 2, outCArray);  // copy
   return outJNIArray;
}

JNI基本類型的array函數

JNI基本類型的array(jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray 和 jbooleanArray)函數如下:

// ArrayType: jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray
// PrimitiveType: int, byte, short, long, float, double, char, boolean
// NativeType: jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean
NativeType * Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);
void Release<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, NativeType *elems, jint mode);
void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, NativeType *buffer);
void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, const NativeType *buffer);
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);

同樣地,在get函數和release函數之間也不能always block。

訪問Java對象變量和回調Java方法

訪問Java對象實例的變量

JNI程序:TestJNIInstanceVariable.java

public class TestJNIInstanceVariable {
   static {
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }

   // Instance variables
   private int number = 88;
   private String message = "Hello from Java";

   // Declare a native method that modifies the instance variables
   private native void modifyInstanceVariable();

   // Test Driver   
   public static void main(String args[]) {
      TestJNIInstanceVariable test = new TestJNIInstanceVariable();
      test.modifyInstanceVariable();
      System.out.println("In Java, int is " + test.number);
      System.out.println("In Java, String is " + test.message);
   }
}

這個類包含了兩個private實例變量,一個int,一個String對象。然後我們在main中調用本地函數modifyInstanceVariable來修改這兩個變量。
C代碼實現:TestJNIInstanceVariable.c

#include <jni.h>
#include <stdio.h>
#include "TestJNIInstanceVariable.h"

JNIEXPORT void JNICALL Java_TestJNIInstanceVariable_modifyInstanceVariable
          (JNIEnv *env, jobject thisObj) {
   // Get a reference to this object's class
   jclass thisClass = (*env)->GetObjectClass(env, thisObj);

   // int
   // Get the Field ID of the instance variables "number"
   jfieldID fidNumber = (*env)->GetFieldID(env, thisClass, "number", "I");
   if (NULL == fidNumber) return;

   // Get the int given the Field ID
   jint number = (*env)->GetIntField(env, thisObj, fidNumber);
   printf("In C, the int is %d\n", number);

   // Change the variable
   number = 99;
   (*env)->SetIntField(env, thisObj, fidNumber, number);

   // Get the Field ID of the instance variables "message"
   jfieldID fidMessage = (*env)->GetFieldID(env, thisClass, "message", "Ljava/lang/String;");
   if (NULL == fidMessage) return;

   // String
   // Get the object given the Field ID
   jstring message = (*env)->GetObjectField(env, thisObj, fidMessage);

   // Create a C-string with the JNI String
   const char *cStr = (*env)->GetStringUTFChars(env, message, NULL);
   if (NULL == cStr) return;

   printf("In C, the string is %s\n", cStr);
   (*env)->ReleaseStringUTFChars(env, message, cStr);

   // Create a new C-string and assign to the JNI string
   message = (*env)->NewStringUTF(env, "Hello from C");
   if (NULL == message) return;

   // modify the instance variables
   (*env)->SetObjectField(env, thisObj, fidMessage, message);
}

爲了訪問對象中的變量,我們需要:
1. 調用GetObjectClass()獲得目標對象的類引用
2. 從上面獲得的類引用中獲得Field ID來訪問變量,你需要提供這個變量的名字,變量的描述符(也稱爲簽名)。對於java類而言,描述符是這樣的形式:“Lfully-qualified-name;”(注意最後有一個英文半角分號),其中的包名點號換成斜槓(/),比如java的Stirng類的描述符就是“Ljava/lang/String;”。對於基本類型而言,I代表int,B代表byte,S代表short,J代表long,F代表float,D代表double,C代表char,Z代表boolean。對於array而言,使用左中括號”[“來表示,比如“[Ljava/lang/Object;”表示Object的array,“[I”表示int型的array。
3. 基於上面獲得的Field ID,使用GetObjectField() 或者 Get_primitive-type_Field()函數來從中解析出我們想要的數據
4. 使用SetObjectField() 或者 Set_primitive-type_Field()函數來修改變量
JNI中用來訪問實例變量的函數有:

jclass GetObjectClass(JNIEnv *env, jobject obj);
   // Returns the class of an object.

jfieldID GetFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);
  // Returns the field ID for an instance variable of a class.

NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);
  // Get/Set the value of an instance variable of an object
  // <type> includes each of the eight primitive types plus Object.

訪問類中的static變量

訪問類中的static變量類似於上面訪問普通的實例變量,只是我們這裏使用的函數是GetStaticFieldID(), Get|SetStaticObjectField(), Get|SetStatic_Primitive-type_Field()。
JNI 程序: TestJNIStaticVariable.java

public class TestJNIStaticVariable {
   static {
      System.loadLibrary("myjni"); // nyjni.dll (Windows) or libmyjni.so (Unixes)
   }

   // Static variables
   private static double number = 55.66;

   // Declare a native method that modifies the static variable
   private native void modifyStaticVariable();

   // Test Driver
   public static void main(String args[]) {
      TestJNIStaticVariable test = new TestJNIStaticVariable();
      test.modifyStaticVariable();
      System.out.println("In Java, the double is " + number);
   }
}

C語言實現:C Implementation - TestJNIStaticVariable.c

#include <jni.h>
#include <stdio.h>
#include "TestJNIStaticVariable.h"

JNIEXPORT void JNICALL Java_TestJNIStaticVariable_modifyStaticVariable
          (JNIEnv *env, jobject thisObj) {
   // Get a reference to this object's class
   jclass cls = (*env)->GetObjectClass(env, thisObj);

   // Read the int static variable and modify its value
   jfieldID fidNumber = (*env)->GetStaticFieldID(env, cls, "number", "D");
   if (NULL == fidNumber) return;
   jdouble number = (*env)->GetStaticDoubleField(env, cls, fidNumber);
   printf("In C, the double is %f\n", number);
   number = 77.88;
   (*env)->SetStaticDoubleField(env, cls, fidNumber, number);
}

JNI中用來訪問類中的static變量的函數如下:

jfieldID GetStaticFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);
  // Returns the field ID for a static variable of a class.

NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID);
void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value);
  // Get/Set the value of a static variable of a class.
  // <type> includes each of the eight primitive types plus Object.

回調實例的普通和static方法

你可以在native代碼中回調java中的普通或者static的方法。下面是實例:
JNI程序:TestJNICallBackMethod.java

public class TestJNICallBackMethod {
   static {
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }

   // Declare a native method that calls back the Java methods below
   private native void nativeMethod();

   // To be called back by the native code
   private void callback() {
      System.out.println("In Java");
   }

   private void callback(String message) {
      System.out.println("In Java with " + message);
   }

   private double callbackAverage(int n1, int n2) {
      return ((double)n1 + n2) / 2.0;
   }

   // Static method to be called back
   private static String callbackStatic() {
      return "From static Java method";
   }

   // Test Driver 
   public static void main(String args[]) {
      new TestJNICallBackMethod().nativeMethod();
   }
}

這個類中聲明瞭一個native函數nativeMethod(),並且在main方法中調用了這個函數。nativeMethod()這個函數會回調這個類中定義的各種方法。
C語言實現:TestJNICallBackMethod.c

#include <jni.h>
#include <stdio.h>
#include "TestJNICallBackMethod.h"

JNIEXPORT void JNICALL Java_TestJNICallBackMethod_nativeMethod
          (JNIEnv *env, jobject thisObj) {

   // Get a class reference for this object
   jclass thisClass = (*env)->GetObjectClass(env, thisObj);

   // Get the Method ID for method "callback", which takes no arg and return void
   jmethodID midCallBack = (*env)->GetMethodID(env, thisClass, "callback", "()V");
   if (NULL == midCallBack) return;
   printf("In C, call back Java's callback()\n");
   // Call back the method (which returns void), baed on the Method ID
   (*env)->CallVoidMethod(env, thisObj, midCallBack);

   jmethodID midCallBackStr = (*env)->GetMethodID(env, thisClass,
                               "callback", "(Ljava/lang/String;)V");
   if (NULL == midCallBackStr) return;
   printf("In C, call back Java's called(String)\n");
   jstring message = (*env)->NewStringUTF(env, "Hello from C");
   (*env)->CallVoidMethod(env, thisObj, midCallBackStr, message);

   jmethodID midCallBackAverage = (*env)->GetMethodID(env, thisClass,
                                  "callbackAverage", "(II)D");
   if (NULL == midCallBackAverage) return;
   jdouble average = (*env)->CallDoubleMethod(env, thisObj, midCallBackAverage, 2, 3);
   printf("In C, the average is %f\n", average);

   jmethodID midCallBackStatic = (*env)->GetStaticMethodID(env, thisClass,
                                 "callbackStatic", "()Ljava/lang/String;");
   if (NULL == midCallBackStatic) return;
   jstring resultJNIStr = (*env)->CallStaticObjectMethod(env, thisClass, midCallBackStatic);
   const char *resultCStr = (*env)->GetStringUTFChars(env, resultJNIStr, NULL);
   if (NULL == resultCStr) return;
   printf("In C, the returned string is %s\n", resultCStr);
   (*env)->ReleaseStringUTFChars(env, resultJNIStr, resultCStr);
}

爲了能夠回調實例中的方法,我們需要:
1. 通過GetObjectClass()函數獲得這個實例的類對象
2. 從上面獲得類對象中,調用GetMethodID()函數來獲得Method ID,Method ID表示了實例中的某個方法的抽象。你需要提供這個方法的名字和簽名信息,簽名規則和變量類似。簽名的格式是這樣的:(parameters)return-type。如果我們實在覺得jni的簽名不好記憶的話,我們可以是用JDK爲我們提供的工具javap來獲得某個class類中的所有方法的簽名,使用-s選項表示打印簽名,-p表示顯示private成員:

> javap --help
> javap -s -p TestJNICallBackMethod
  .......
  private void callback();
    Signature: ()V

  private void callback(java.lang.String);
    Signature: (Ljava/lang/String;)V

  private double callbackAverage(int, int);
    Signature: (II)D

  private static java.lang.String callbackStatic();
    Signature: ()Ljava/lang/String;
  .......

從上面的輸出我們可以清楚地看到類中每一個方法的簽名。
3. 基於上面我們獲得的Method ID,我們可以調用_Primitive-type_Method() 或者 CallVoidMethod() 或者 CallObjectMethod()來調用這個方法。如果某個方法需要參數的話,就在後面跟上參數即可。
4. 如果想要調用一個static方法的話,使用GetMethodID(), CallStatic_Primitive-type_Method(), CallStaticVoidMethod() 或者 CallStaticObjectMethod()。
JNI中用來回調實例和static方法的所有函數(兩類,普通的和static的):

jmethodID GetMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig);
   // Returns the method ID for an instance method of a class or interface.

NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...);
NativeType Call<type>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
NativeType Call<type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
   // Invoke an instance method of the object.
   // The <type> includes each of the eight primitive and Object.

jmethodID GetStaticMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig);
   // Returns the method ID for an instance method of a class or interface.

NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
   // Invoke an instance method of the object.
   // The <type> includes each of the eight primitive and Object.

回調複寫的父類實例方法

JNI提供了一系列的形如 CallNonvirtual_Type_Method()之類的函數來調用父類實例的方法:
1. 首先獲得Method ID,使用GetMethodID()
2. 基於上獲得的Method ID,通過調用 CallNonvirtual_Type_Method()函數來調用相應的方法,並且在參數中給出object,父類和參數列表。
JNI中用來訪問父類方法的函數:

NativeType CallNonvirtual<type>Method(JNIEnv *env, jobject obj, jclass cls, jmethodID methodID, ...);
NativeType CallNonvirtual<type>MethodA(JNIEnv *env, jobject obj, jclass cls, jmethodID methodID, const jvalue *args);
NativeType CallNonvirtual<type>MethodV(JNIEnv *env, jobject obj, jclass cls, jmethodID methodID, va_list args);

創建Object和Object arrays

你可以在native代碼中構造jobject和jobjectarray,通過調用NewObject() 和 newObjectArray()函數,然後講它們返回給java代碼。

回調Java構造器來創建一個新的java對象

回調一個構造器和回調其他的方法是類似的,首先通過init作爲方法名,V作爲返回值來獲得Method ID,然後通過NewObject()函數來構建一個java類對象。
JNI程序:TestJavaConstructor.java

public class TestJNIConstructor {
   static {
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }

   // Native method that calls back the constructor and return the constructed object.
   // Return an Integer object with the given int.
   private native Integer getIntegerObject(int number);

   public static void main(String args[]) {
      TestJNIConstructor obj = new TestJNIConstructor();
      System.out.println("In Java, the number is :" + obj.getIntegerObject(9999));
   }
}

這個類聲明瞭一個getIntegerObject的native方法,這個方法接受一個int的數據,然後在native代碼中創建一個Integer類型的對象,其中的值就是這個值。
C代碼實現:TestJavaConstructor.c

#include <jni.h>
#include <stdio.h>
#include "TestJNIConstructor.h"

JNIEXPORT jobject JNICALL Java_TestJNIConstructor_getIntegerObject
          (JNIEnv *env, jobject thisObj, jint number) {
   // Get a class reference for java.lang.Integer
   jclass cls = (*env)->FindClass(env, "java/lang/Integer");

   // Get the Method ID of the constructor which takes an int
   jmethodID midInit = (*env)->GetMethodID(env, cls, "<init>", "(I)V");
   if (NULL == midInit) return NULL;
   // Call back constructor to allocate a new instance, with an int argument
   jobject newObj = (*env)->NewObject(env, cls, midInit, number);

   // Try runnning the toString() on this newly create object
   jmethodID midToString = (*env)->GetMethodID(env, cls, "toString", "()Ljava/lang/String;");
   if (NULL == midToString) return NULL;
   jstring resultStr = (*env)->CallObjectMethod(env, newObj, midToString);
   const char *resultCStr = (*env)->GetStringUTFChars(env, resultStr, NULL);
   printf("In C: the number is %s\n", resultCStr);

   return newObj;
}

JNI中用於創建對象(jobject)的函數有:

jclass FindClass(JNIEnv *env, const char *name);

jobject NewObject(JNIEnv *env, jclass cls, jmethodID methodID, ...);
jobject NewObjectA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args);
jobject NewObjectV(JNIEnv *env, jclass cls, jmethodID methodID, va_list args);
   // Constructs a new Java object. The method ID indicates which constructor method to invoke

jobject AllocObject(JNIEnv *env, jclass cls);
  // Allocates a new Java object without invoking any of the constructors for the object.

對象(object)的array

JNI程序:TestJNIObjectArray.java

import java.util.ArrayList;

public class TestJNIObjectArray {
   static {
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }
   // Native method that receives an Integer[] and
   //  returns a Double[2] with [0] as sum and [1] as average
   private native Double[] sumAndAverage(Integer[] numbers);

   public static void main(String args[]) {
      Integer[] numbers = {11, 22, 32};  // auto-box
      Double[] results = new TestJNIObjectArray().sumAndAverage(numbers);
      System.out.println("In Java, the sum is " + results[0]);  // auto-unbox
      System.out.println("In Java, the average is " + results[1]);
   }
}

爲了簡單起見,這個類聲明瞭一個native方法,這個方法接受一個Integer類型的array,然後在本地代碼中計算這個array中的數的和與平均數,然後講這兩個數以Double array的形式返回。
C代碼實現:TestJNIObjectArray.c

#include <jni.h>
#include <stdio.h>
#include "TestJNIObjectArray.h"

JNIEXPORT jobjectArray JNICALL Java_TestJNIObjectArray_sumAndAverage
          (JNIEnv *env, jobject thisObj, jobjectArray inJNIArray) {
   // Get a class reference for java.lang.Integer
   jclass classInteger = (*env)->FindClass(env, "java/lang/Integer");
   // Use Integer.intValue() to retrieve the int
   jmethodID midIntValue = (*env)->GetMethodID(env, classInteger, "intValue", "()I");
   if (NULL == midIntValue) return NULL;

   // Get the value of each Integer object in the array
   jsize length = (*env)->GetArrayLength(env, inJNIArray);
   jint sum = 0;
   int i;
   for (i = 0; i < length; i++) {
      jobject objInteger = (*env)->GetObjectArrayElement(env, inJNIArray, i);
      if (NULL == objInteger) return NULL;
      jint value = (*env)->CallIntMethod(env, objInteger, midIntValue);
      sum += value;
   }
   double average = (double)sum / length;
   printf("In C, the sum is %d\n", sum);
   printf("In C, the average is %f\n", average);

   // Get a class reference for java.lang.Double
   jclass classDouble = (*env)->FindClass(env, "java/lang/Double");

   // Allocate a jobjectArray of 2 java.lang.Double
   jobjectArray outJNIArray = (*env)->NewObjectArray(env, 2, classDouble, NULL);

   // Construct 2 Double objects by calling the constructor
   jmethodID midDoubleInit = (*env)->GetMethodID(env, classDouble, "<init>", "(D)V");
   if (NULL == midDoubleInit) return NULL;
   jobject objSum = (*env)->NewObject(env, classDouble, midDoubleInit, (double)sum);
   jobject objAve = (*env)->NewObject(env, classDouble, midDoubleInit, average);
   // Set to the jobjectArray
   (*env)->SetObjectArrayElement(env, outJNIArray, 0, objSum);
   (*env)->SetObjectArrayElement(env, outJNIArray, 1, objAve);

   return outJNIArray;
}

不像基本數據類型的array那樣,你需要使用Get|SetObjectArrayElement()函數來處理每一個元素。
JNI提供了創建對象array(jobjectArray)的函數如下:

jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);
   // Constructs a new array holding objects in class elementClass.
   // All elements are initially set to initialElement.

jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);
   // Returns an element of an Object array.

void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);
   // Sets an element of an Object array.

本地和全局引用

管理引用是編寫高效程序的關鍵。比如,我們會在本地代碼中經常使用FindClass(), GetMethodID(), GetFieldID()來會的一個jclass,jmethodID和jfieldID。其實這些變量應該只是在第一次的時候獲取,之後直接使用就可以了,而不用每次都去獲取一遍,這樣可以提高程序執行效率。
JNI講本地代碼中的對象引用分爲了兩種類型: 本地和全局引用:
1. 本地引用是在本地代碼中創建的,並且當函數退出或者返回的時候就被free了。它的有效範圍只是這個native函數的內部。你也可以調用DeleteLocalRef()來顯式地將某個本地引用作廢,這樣可以讓垃圾回收時能夠將這部分回收。作爲參數傳遞到本地函數中的對象引用是本地引用,所有的從JNI函數返回的java對象(jobject)都是本地引用。
2. 全局引用會保留直到程序員調用DeleteGlobalRef()手動free掉他們,你可以使用NewGlobalRef()函數從本地引用創建一個全局引用。
下面我們給出一個例子。

public class TestJNIReference {
   static {
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }

   // A native method that returns a java.lang.Integer with the given int.
   private native Integer getIntegerObject(int number);

   // Another native method that also returns a java.lang.Integer with the given int.
   private native Integer anotherGetIntegerObject(int number);

   public static void main(String args[]) {
      TestJNIReference test = new TestJNIReference();
      System.out.println(test.getIntegerObject(1));
      System.out.println(test.getIntegerObject(2));
      System.out.println(test.anotherGetIntegerObject(11));
      System.out.println(test.anotherGetIntegerObject(12));
      System.out.println(test.getIntegerObject(3));
      System.out.println(test.anotherGetIntegerObject(13));
   }
}

上面的JNI程序聲明瞭兩個native函數,這兩個都創建並且返回java.lang.Integer對象。在C代碼實現中,我們需要獲得java.lang.Integer的類引用,然後我們從中找到構造器的method ID,然後調用構造器。然而,我們希望,將我們獲得的class引用和Method ID緩存起來,這樣我們下次在使用的時候就不用再次去獲取了。
下面是我們的C代碼,我們希望這樣可以OK(然而事實是不行!!!):

#include <jni.h>
#include <stdio.h>
#include "TestJNIReference.h"

// Global Reference to the Java class "java.lang.Integer"
static jclass classInteger;
static jmethodID midIntegerInit;

jobject getInteger(JNIEnv *env, jobject thisObj, jint number) {

   // Get a class reference for java.lang.Integer if missing
   if (NULL == classInteger) {
      printf("Find java.lang.Integer\n");
      classInteger = (*env)->FindClass(env, "java/lang/Integer");
   }
   if (NULL == classInteger) return NULL;

   // Get the Method ID of the Integer's constructor if missing
   if (NULL == midIntegerInit) {
      printf("Get Method ID for java.lang.Integer's constructor\n");
      midIntegerInit = (*env)->GetMethodID(env, classInteger, "<init>", "(I)V");
   }
   if (NULL == midIntegerInit) return NULL;

   // Call back constructor to allocate a new instance, with an int argument
   jobject newObj = (*env)->NewObject(env, classInteger, midIntegerInit, number);
   printf("In C, constructed java.lang.Integer with number %d\n", number);
   return newObj;
}

JNIEXPORT jobject JNICALL Java_TestJNIReference_getIntegerObject
          (JNIEnv *env, jobject thisObj, jint number) {
   return getInteger(env, thisObj, number);
}

JNIEXPORT jobject JNICALL Java_TestJNIReference_anotherGetIntegerObject
          (JNIEnv *env, jobject thisObj, jint number) {
   return getInteger(env, thisObj, number);
}

在上面的程序中,我們調用FindClass()獲得了java.lang.Integer類引用,然後把它保存在一個全局靜態的變量中。然而,在第二次調用中這個引用卻無效了(並不是NULL)。這是因爲FindClass()返回的是本地類引用,一旦當getInteger函數返回的時候,這個局部引用就失效了。
爲了解決這個問題,我們需要從局部引用中創建一個全局引用,然後再賦值給全局static變量:

// Get a class reference for java.lang.Integer if missing
if (NULL == classInteger) {
   printf("Find java.lang.Integer\n");
   // FindClass returns a local reference
   jclass classIntegerLocal = (*env)->FindClass(env, "java/lang/Integer");
   // Create a global reference from the local reference
   classInteger = (*env)->NewGlobalRef(env, classIntegerLocal);
   // No longer need the local reference, free it!
   (*env)->DeleteLocalRef(env, classIntegerLocal);
}

需要注意的是,jmethodID和jfieldID並不是jobject,因此他們不能創建一個全局引用!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章