JNI 自定义类型参数转换

  在使用java的jni调用C++接口时候, 我们会先把数据转换成基本类型, 比如int, float, double, int[]等等, 一方面减低参数类型转换上的繁琐, 另一方面也许也能减少耦合. 实际应用时候, 可能会遇到希望一个jni接口返回多个参数的情况, 这情况下, 貌似就不得不用自定义类型参数的转换了. 这篇文章, 介绍的就是一个自定义类型参数转换的android例子程序.

  这个例子是二维数组自定义类的, 其实三维四维甚至更多纬的数组的使用方法都是如此类推的.遇到这种需求时候,就能轻松加愉快的解决了.

  关于android jni ndk编程入门, 可以参考这篇文章.

 

1. 在java代码中加入一个返回自定义类型的native接口:

package com.jnitest;

import android.app.Activity;
import android.os.Bundle;

public class JnitestActivity extends Activity {
    /** Called when the activity is first created. */
	
	static
	{
		System.loadLibrary("test-jni");
	}
	
	native static PointF[][] createPointFs(int len1, int len2);  //返回PointF的数组,PointF是一个自定义的类
	
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        PointF[][] points = createPointFs(2, 2);
        for (int i = 0; i < points.length; i++)
        {
        	for (int j = 0; j < points[0].length; j++)
        	{
        		System.out.println(points[i][j].x + "," + points[i][j].y);
        	}
        }
    }
}

  

下面是PointF.java

package com.jnitest;

public class PointF
{
public float x;
public float y;

public PointF(float xx, float yy)
{
x = xx;
y = yy;
}
}

 

2. 使用javah命令生成C/C++的头文件.(这篇文章有介绍如何使用javah命令生成头文件)

 生成下图红色框框内的头文件

 

3. 实现.cpp文件,代码如下:

#include "com_jnitest_JnitestActivity.h"
#include <stdio.h>

JNIEXPORT jobjectArray JNICALL Java_com_jnitest_JnitestActivity_createPointFs
(JNIEnv *jenv, jclass jcls, jint jlen1, jint jlen2)
{
//convert parameter to C/C++ type
int len1 = (int) jlen1;
int len2 = (int) jlen2;

//create java type PointF
jclass objectClass = (jenv)->FindClass("com/jnitest/PointF");
jobjectArray jpointfs1 = (jenv)->NewObjectArray((jsize) len2, objectClass, NULL);
jobjectArray pointfArrayArray = (jenv)->NewObjectArray((jsize) len1, (jenv)->GetObjectClass(jpointfs1), NULL);
jmethodID cid = (jenv)->GetMethodID(objectClass, "<init>", "(FF)V");

for (int j = 0; j < len1; j++)
{
jobjectArray jpointfarray = (jenv)->NewObjectArray((jsize) len2, objectClass, NULL);
for (int i = 0; i < len2; i++)
{
jfloat jx = (jfloat) i;
jfloat jy = (jfloat) j;
jobject pointF = (jenv)->NewObject(objectClass, cid, jx, jy);
(jenv)->SetObjectArrayElement(jpointfarray, i, pointF);
(jenv)->DeleteLocalRef(pointF);
}
(jenv)->SetObjectArrayElement(pointfArrayArray, j, jpointfarray);
(jenv)->DeleteLocalRef(jpointfarray);
}
(jenv)->DeleteLocalRef(jpointfs1);

return pointfArrayArray;
}


下面一点一点的解释上面这些代码:

1) int len1 = (int) jlen1;  

    把java的int类型转换成,C/C++的int类型,直接转换就可以了.

 

2) jclass objectClass = (jenv)->FindClass("com/jnitest/PointF");

    获得java自定义类PointF, FindClass的参数,指明的是从source file开始的PointF类的路径

 

3) jobjectArray jpointfs1 = (jenv)->NewObjectArray((jsize) len2, objectClass, NULL);

    创建个PointF的数组(参数objectClass指明了这个数组的元素类型), 数组的长度为len2

 

4) jobjectArray pointfArrayArray = (jenv)->NewObjectArray((jsize) len1, (jenv)->GetObjectClass(jpointfs1), NULL);

    这里是创建一个以PointF的数组为元素的数组, 实际上它是一个PointF的二维数组, 因为上面定义的java native函数返回的就是一个PointF[][].

 

5) jmethodID cid = (jenv)->GetMethodID(objectClass, "<init>", "(FF)V");

    获得PointF构造函数的ID.

    "<init>",这里指明的是函数名字,构造函数就写"<init>".

    "(FF)V",圆括号内表示参数类型,FF代表,有两个参数都是float类型.圆括号后的V,代表函数返回类型是void.关于函数签名, 这里这里都有比较详细的对照表和说明.

 

6) jobject pointF = (jenv)->NewObject(objectClass, cid, jx, jy);

    创建一个PointF类型的对象. objectClass是要创建的对象的类型, cid是构造函数的ID, jx和jy是构造函数的两个参数.

 

7) (jenv)->SetObjectArrayElement(jpointfarray, i, pointF);

    这行代码就容易理解, 往jpointfarray数组给元素赋值, i 是要赋值元素的下标, pointF就是要赋的值.

 

8) (jenv)->DeleteLocalRef(pointF);

    删除引用计数. 这里为什么要删除引用技术呢, 因为(6)里面每次都会new一个对象, 引用计数会加1, 当引用技术超过某个数(好像是500,具体忘记了)就会crash. 这篇文章介绍了LocalRef这个问题.

 

4. 编译so文件,并运行程序,查看logcat中的输出如下.

    可以看到创建了一个2*2的二位数组

posted on 2012-01-15 11:28  wingyip  阅读(2679)  评论(0编辑  收藏  举报

导航