1. 程式人生 > >JNI介面實現Java和C的互動

JNI介面實現Java和C的互動

        當面對帶有原生代碼的 Java 的應用程式時,程式設計師問的最通常的問之一,是在 Java 程式語言中的資料型別怎樣對映到本地程式語言C和C++中的資料型別。實際上,大多數程式將需要傳遞引數給本地方法,和也從本地方法接受結果。

1、基本型別的對映
        在本地方法宣告中引數型別有對應的在本地程式語言中的型別。 JNI定義了一套C和C++型別來對應在Java程式語言中的型別。
        在Java程式語言中的兩種型別:基本來型如int,float,和char和參考型別如classes,instances和arrays.在Java程式語言中, strings 是java.lang.String類的一個例項。
        JNI不同地對待基本型別和參考型別。基本型別的對映是簡單易懂的。例如,在Java程式語言中的int型別對映到C/C++的jint型別(定義在jni.h作為一個有符號 32bit 整型),同時在Java程式語言中的float類對映到C++的jfloat型別(定義在jni.h作為一個有符號 32bit浮點數)。基本型別都有它們對應的表述:


        JNI傳遞objects到本地方法作為不透明的引用(opaque references)。 不透明的引用是一個 C 指標型別,引用了在 Java 虛擬機器中的內部資料結構。然而,內部資料結構的精確安排,對程式設計者是隱藏的。原生代碼必須通過恰當的JNI函式處理下面的物件(objects), JNI函式通過JNIEnv介面指標是可用的。例如,為java.lang.String對應的JNI型別是jstring。一個jstring引用(reference)的確切值是和原生代碼無關的。原生代碼呼叫JNI函式例如GetStringUTFChars來訪
問一個 string 的內容。
        所有的JNI引用都是型別 jobject。為了方便和增強型別的安全, JNI定義了一組引用類,它們概念上為jobject的子型別(subtypes).( A 是 B 的子類, A 的例項也是 B 的例項。 )這些子類對應在Java程式語言中常用地引用型別。例如, jstring指示strings;jobjextArray指示一組objects。 

2、訪問 String
        jstring型別表示在 Java虛擬機器中的strings,同時和規定的“C string型別不同(指向字元的指標, char *)。本地方法程式碼必須使用恰當的JNI函式來轉化jstirng objects為C/C++ strings。 JNI支援轉換到或從Unicode和UTF-8的strings。
//java程式碼
package com.igood.ndk.hello;

public class Prompt {
	//宣告載入的C庫中的本地方法
	private native String getLine(String prompt) ;
	static {
		System.loadLibrary("Prompt") ;
	}
}

//C語言實現的jni
#include <stdio.h>
#include <stdlib.h>
#include "com_igood_ndk_hello_Prompt.h"

JNIEXPORT jstring JNICALL Java_com_igood_ndk_hello_Prompt_getLine
  (JNIEnv *env , jobject thiz, jstring prompt)
{
	char buf[128] ;
	const jbyte *str ;
	str = (*env)->GetStringUTFChars(env, prompt , NULL) ;//java String物件轉換到本地字串
	if (NULL == str){
	return NULL;
	}
	printf("%s", str) ;
	(*env)->ReleaseStringUTFChars(env, prompt, str) ;//釋放指向utf-8格式的char*的指標
	scanf("%s", buf) ;
	return (*env)->NewStringUTF(env, buf) ;//構建新的utf-8格式的字串
}

2.1、java String物件轉換到本地字串
         JNI的函式GetStringUTFChars可以用來閱讀string的內容。通過JNIEnv的介面指標 GetStringUTFChars函式是能被呼叫的。它轉換了作為一個Unicode序列通過 Java 虛擬機器的實現來表示jstring的引用到用UTF8 格式表示的一個C string。

const char* GetStringUTFChars(JNIEnv* env, jstring str, jboolean*isCopy);

不要忘記檢查GetStringUTFChars的返回值。因為 Java 虛擬機器實現需要分配空間來儲存UTF-8string,這有有機會內配失敗。當這樣的事發生時,GetStringUTFChars返回NULL,同時通過JNI丟擲一個異常。

2.2、釋放本地字元資源
  當你原生代碼結束使用通過GetStringUTFChars得到的UTF-8 string時,它呼叫ReleaseStringUTFChars。呼叫ReleaseStringUTFChars指明本地方法不再需要這GetStringUTFChars返回的UTF-8 string了;因此UTF-8 string佔有的記憶體將被釋放。沒有呼叫ReleaseStringUTFChars將導致記憶體洩漏,這將可能最終導致記憶體的耗盡。

void       ReleaseStringUTFChars(JNIEnv*env, jstring str, const char* pchar);
2.3、構建新的字串
jstring    NewStringUTF(JNIEnv*env, const char* pchar);
  在本地方法中通過呼叫JNI函式NewStringUTF,你能構建一個新的java.lang.String例項。NewStringUTF函式使用一個帶有UTF-8格式的C string,同時構建一個java.lang.String例項。最新被構建的java.lang.String例項表現為和被給的UTF-8 C string一樣的Unicode字元序列。
  如果虛擬機器沒有記憶體分配來構建java.lang.String例項, NewStringUTF丟擲一個OutofMemoryError異常,同時返回NULL。
2.4、其他的JNI字串函式
  JNI支援大量的其他字串相關的函式(string-related functions),除了前面介紹的GetStringUTFChars, ReleaseStringUTFChars和NewStringUTF函式。GetStringChars和ReleaseStringChars獲得字串使用Unicode格式。例如,在作業系統支援Unicode 為本地字串格式的時候,這些函式有用。UTF-8 string總是以\0字元結尾, Unicode 字元不是。為在一個jstring引用中得到 Unicode字元的個數, JNI 程式設計師能呼叫GetStringLength。為得到用UTF-8格式表示的一個jstring需要的bytes數,JNI程式設計師可以呼叫 ASCII C 函式 strlen 在GetStringUTFChars的結果上,或直接地呼叫JNI函式GetStringUTFLength在jstring引用上。
  • GetStringUTFLengt獲取 UTF-8格式的char*的長度。
        jsize  GetStringUTFLength(JNIEnv*env, jstring str);
  • GetStringChars將jstring轉換成為Unicode格式的char*。
       const jchar* GetStringChars(JNIEnv*env, jstring str, jboolean*isCopy);
  • ReleaseStringChars釋放指向Unicode格式的char*的指標。
       void  ReleaseStringChars(JNIEnv*env, jstring str, const jchar* pchar);
  • NewString建立一個Unicode格式的String物件。
      jstring NewString(JNIEnv*env, const jchar*pchar, jsize size);
  • GetStringLength獲取Unicode格式的char*的長度。
      jsize  GetStringLength(JNIEnv*env, jstring str);
  GetStringChars和GetStringUTFChar的第三個引數isCopy的需要額外的解釋:
  當來自GetStringChars返回時,通過isCopy被設定為JNI_TRUE,返回的字串是個在原始java.lang.String例項的字串的一個複製,記憶體定位指向它。通過isCopy被設定為JNI_FALSE,返回字串時一個直接指向在原始的java.lang.String例項中的字串,記憶體定位指向它。當通過isCopy被設定為JNI_FALSE,記憶體定位指向的字串時,原生代碼必須不能修改返回的字串的內容。違反了這個規則將引起原始java.lang.String例項也被修改。這破壞了java.lang.String永恆不可變性。
  最常傳遞NULL作為isCopy引數,因為你不關心 Java 虛擬機器是否返回在java.lang.String例項中的一個複製字串和直接指向原始字串。
  不要忘記呼叫ReleaseStringChars,當你不再需要訪問來自GetStringChars返回的string元素的時候。 ReleaseStringChars呼叫是必須的,無論GetStringChars設定* isCopy為JNI_TRUE或JNI_FALSE。 ReleaseStringChars釋放副本,或解除例項的固定,這依賴GetStringChars是返回一個副本還是指向例項。 3、訪問陣列

  和String物件一樣,在本地方法 中不能直接訪問jarray物件,而是使用JNIEnv指標指向的一些方法來使用。JNI對待基本的陣列和物件陣列是不同地。基礎陣列包含原始是基本型別的例如int和boolean。物件陣列(Object arrays)包含元素是應用型別例如 class 例項和其他陣列。

3.1、訪問Java原始型別陣列:

  1)獲取陣列的長度:

jsize GetArrayLength(JNIEnv*env, jarray arr);
這裡獲取陣列的長度和普通的c語言中的獲取陣列長度的函式不一樣,這裡使用JNIEvn的一個函式 GetArrayLength。
2)獲取一個指向int陣列元素的指標:
jint* GetIntArrayElements(JNIEnv*env, jintArray arr, jboolean*isCopy);
使用 GetIntArrayElements方法獲取指向arr陣列元素的指標,注意該函式的引數,第一個引數是JNIEnv,第二個引數是陣列,第三個引數是是否通過拷貝原來陣列。

 3)釋放陣列元素的引用

void  ReleaseIntArrayElements(JNIEnv*env, jintArray arr,
                        jint* pArr, jint mode);
  和操作String中的釋放String的引用是一樣的,提醒JVM回收arr陣列元素的引用。

  這裡舉的例子是使用int陣列的,同樣還有boolean、float等對應的陣列。
  獲取陣列元素指標的對應關係:

函式

Java 陣列型別

本地型別

GetBooleanArrayElements

jbooleanArray

jboolean

GetByteArrayElements

jbyteArray

jbyte

GetCharArrayElements

jcharArray

jchar

GetShortArrayElements

jshortArray

jshort

GetIntArrayElements

jintArray

jint

GetLongArrayElements

jlongArray

jlong

GetFloatArrayElements

jfloatArray

jfloat

GetDoubleArrayElements

jdoubleArray

jdouble


函式

Java 陣列型別

本地型別

ReleaseBooleanArrayElements

jbooleanArray

jboolean

ReleaseByteArrayElements

jbyteArray

jbyte

ReleaseCharArrayElements

jcharArray

jchar

ReleaseShortArrayElements

jshortArray

jshort

ReleaseIntArrayElements

jintArray

jint

ReleaseLongArrayElements

jlongArray

jlong

ReleaseFloatArrayElements

jfloatArray

jfloat

ReleaseDoubleArrayElements

jdoubleArray

jdouble


package com.igood.ndk.hello;

public class NativeClass {
	//靜態程式碼塊在類載入時會執行,這個時候就會載入本地的C庫檔案
	static{
		//載入本地的C庫檔案
		System.loadLibrary("helloAndroidNDK");
	}
	//宣告載入的C庫中的本地方法,獲得int型別陣列
	public native int[] getIntegerArray(int length);
	//宣告載入的C庫中的本地方法,設定int型別陣列,返回陣列元素的和
	public native int setIntegerArray(int[] pIntArray);
 
}
//C標頭檔案
#include <jni.h>
/* Header for class com_igood_ndk_hello_NativeClass */

#ifndef _Included_com_igood_ndk_hello_NativeClass
#define _Included_com_igood_ndk_hello_NativeClass
#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:     com_igood_ndk_hello_NativeClass
 * Method:    getIntegerArray
 * Signature: (I)[I
 */
JNIEXPORT jintArray JNICALL Java_com_igood_ndk_hello_NativeClass_getIntegerArray
  (JNIEnv *, jobject, jint);

/*
 * Class:     com_igood_ndk_hello_NativeClass
 * Method:    setIntegerArray
 * Signature: ([I)I
 */
JNIEXPORT jint JNICALL Java_com_igood_ndk_hello_NativeClass_setIntegerArray
  (JNIEnv *, jobject, jintArray);

#ifdef __cplusplus
}
#endif
#endif
//C實現程式碼
#include <stdio.h>
#include <stdlib.h>
#include "com_igood_ndk_hello_NativeClass.h"

JNIEXPORT jintArray JNICALL Java_com_igood_ndk_hello_NativeClass_getIntegerArray
  (JNIEnv *env, jobject thiz, jint length)
{
	int i = 0;
	jintArray outArray = (*env)->NewIntArray(env, length);
	jint *a = (jint*)malloc(length*sizeof(jint));
	if(a == NULL)
	{
		return NULL;
	}
	for (i = 0; i < length; i++) {
		   a[i] = i;
	}
	(*env)->SetIntArrayRegion(env,outArray,0,(jsize)length,a);
	return outArray;
}

JNIEXPORT jint JNICALL Java_com_igood_ndk_hello_NativeClass_setIntegerArray
  (JNIEnv *env, jobject thiz, jintArray inArr)
{
	jint *jint_arr;
	jboolean jbIsCopy = JNI_TRUE;
	jint sum = 0;
	int i = 0;
	jint_arr = (*env)->GetIntArrayElements(env, inArr, &jbIsCopy);
	if (jint_arr == NULL) {
		return 0;
	}
	//獲取陣列的長度
	jsize len =         (*env)->GetArrayLength(env,inArr);
	for(i = 0;i< len;i++)
	{
		sum += jint_arr[i];
	}
	(*env)->ReleaseIntArrayElements( env,inArr,jint_arr,0 );
	return sum;
}
//android測試程式碼
package com.igood.ndk.hello;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class MainActivity extends Activity {
	private TextView tvMsg;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		NativeClass nc = new NativeClass();
		
		int []arr = nc.getIntegerArray(10);
		for(int i = 0; i < 10;i++)
		{
			Log.d("TAG", "arr["+i+"]="+arr[i]);
		}
		
		int[] pIntArray = new int[20];
		for(int i = 0;i<20;i++){
			pIntArray[i] = i + 5;
		}		
		Log.d("TAG", "setIntegerArray="+ nc.setIntegerArray(pIntArray));
	}

}

   執行結果: