当面对带有本地代码的 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。
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是返回一个副本还是指向实例。
和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));
}
}
运行结果: