当面对带有本地代码的 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。
//java代码
package com.igood.ndk.hello;
public class Prompt {
//声明加载的C库中的本地方法
private native String getLine(String prompt) ;
static {
System.loadLibrary("Prompt") ;
}
}
//C语言实现的jni
#include
#include
#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字符序列。
jsize GetStringUTFLength(JNIEnv*env, jstring str);
const jchar* GetStringChars(JNIEnv*env, jstring str, jboolean*isCopy);
void ReleaseStringChars(JNIEnv*env, jstring str, const jchar* pchar);
jstring NewString(JNIEnv*env, const jchar*pchar, jsize size);
jsize GetStringLength(JNIEnv*env, jstring str);
GetStringChars和GetStringUTFChar的第三个参数isCopy的需要额外的解释:
和String对象一样,在本地方法 中不能直接访问jarray对象,而是使用JNIEnv指针指向的一些方法来使用。JNI对待基本的数组和对象数组是不同地。基础数组包含原始是基本类型的例如int和boolean。对象数组(Object arrays)包含元素是应用类型例如 class 实例和其他数组。
3.1、访问Java原始类型数组:
1)获取数组的长度:
jsize GetArrayLength(JNIEnv*env, jarray arr);
这里获取数组的长度和普通的c语言中的获取数组长度的函数
不一样,这里使用JNIEvn的一个函数 GetArrayLength。
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
/* 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
#include
#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));
}
}
运行结果: