关闭

JNI接口实现Java和C的交互

2256人阅读 评论(0) 收藏 举报
分类:

        当面对带有本地代码的 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));
	}

}

   运行结果:



1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:14016次
    • 积分:505
    • 等级:
    • 排名:千里之外
    • 原创:36篇
    • 转载:18篇
    • 译文:0篇
    • 评论:2条
    文章分类
    最新评论