在使用 jni 调用 C++ 接口时候, 先把数据转换成基本类型, 比如 int, float, double, int[] 等等, 一方面减低参数类型转换上的繁琐, 另一方面也许也能减少耦合. 实际应用时候, 可能会遇到希望一个 jni 接口返回多个参数的情况, 可以用自定义类型参数
第一个例子
是二维数组自定义类的, 其实三维四维数组的使用方法都是如此类推
自定义类型的native接口:
native static PointF[][] createPointFs(int len1, int len2); //返回PointF的数组,PointF是一个自定义的类
package com.jnitest;
public class PointF
{
public float x;
public float y;
public PointF(float xx, float yy)
{
x = xx;
y = yy;
}
}
.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);
删除引用计数. 因为每new一个对象, 内存就占用一块, 当占用数量太大就会崩溃,所以用完即删是个好习惯。
第三个例子
基本数据类型参数数组
//基本数据类型参数数组的静态函数
package com.test.jni;
//Java 函数
public class TestFunc{
public static void testFunc(float vals[]){
//...
}
}
//JNI函数回调片段
jclass clz = jenv->FindClass("com/test/jni/TestFunc");
jmethodID methodId = env->GetMethodID(clz,"TestFunc","([F)V");
float[] data = new float[]{1.f,2.f};
//!!错误示例!!
env->CallStaticVoidMethod(clz,methodId,data);
//正确用例
jfloatArray array = env->NewFloatArray(2);
env->SetFloatArrayRegion(array,0,3,data);//参数1:待赋值数组;参数2:赋值起始位置;参数3:赋值长度;参数4:数据源
env->CallStaticVoidMethod(clz,methodId,array);
自定义结构类型参数数组
//自定义类型
package com.test.jni;
public class MyObject{
private String name;
private int number;
//some getters and setters
...
public MyObject(String name, int number){
this.name = name;
this.number = number;
}
}
//自定义类型参数数组的静态函数
package com.test.jni;
//Java 函数
public class TestFunc{
public static void testFunc(MyObject vals[]){
//...
}
}
//JNI函数回调片段
jclass clz = jenv->FindClass("com/test/jni/TestFunc");
jmethodID methodId = jenv->GetMethodID(clz,"TestFunc","([Lcom/test/jni/MyObject;)V");
MyObject myObjects = new MyObject[2];
for(int i = 0; i < myObjects.length; i++){
myObjects[i] = new MyObject("name_" + i, i);
}
//!!错误示例!!
jenv->CallStaticVoidMethod(clz,methodId,myObjects);
//正确用例: 需要在JNI层创建类型数组
jclass jclzMyObject = jenv->FindClass("com/test/jni/MyObject");
jmethodID jmethodConstructID = jenv->GetMethodID(jclzMyObject, "<init>", "()V");
//进行实例创建
jobjectArray array = jenv->NewObjectArray(myObjects.length, jclzMyObject,NULL);
for(int i = 0; i < 2; i++){
jobject jobjMyObj = jenv->NewObject(jclzMyObject, jmethodConstructID);
jfieldID nameField = jenv->GetFieldID(jclzMyObject, "name","Ljava/lang/String;");
jfieldID numberField = jenv->GetFieldID(jclzMyObject,, "number","I");
jenv->SetObjectField(jobjMyObj, nameField, myObjects[i].name);
jenv->SetIntField(jobjMyObj, numberField, myObjects[i].number);
jenv->SetObjectArrayElement(array, i, jobjMyObj);
jenv->DeleteLocalRef(jobjMyObj);
}
//调用类的静态方法
jenv->CallStaticVoidMethod(clz,methodId,array);
通过在JNI层重新创建实例的数组,并将数组进行赋值,此时可以通过该反射调用Java函数的方式将JNI层的数值传递到Java层进行后续的计算处理。有以下需要注意的地方:
- 在JNI层创建实例对象时,需要对Java层该类的构造函数进行有针对性的实现;
- 在JNI层创建实例对象之后,尤其是还需要加入其数组数据结构中,需要在最后进行本地引用的释放,否则在循环创建大量本地引用就会崩溃;
- 针对非静态方法,则需要类似地通过反射在JNI层创建类的实例,通过调用实例函数方式;
第三个例子
一、定义作为输入和返回的自定义类
package com.example.jniexample;
import android.util.Log;
public class JNIParam {
public int mInt;
public String mString;
JNIParam(){
mInt = 0;
mString = "0";
}
public void print(String tag){
Log.d(tag, String.format("print: mInt=%d, mString=%s", mInt, mString));
}
}
二、定义和JNI通信的java封装
package com.example.jniexample;
public class JNIParamTest {
private final String TAG = "JNIParamTest";
static {
System.loadLibrary("JNIParamTest");
};
public void runParamTest(){
JNIParam paramIn = new JNIParam();
paramIn.print(TAG);
JNIParam paramOut = doTest(paramIn);
if( paramOut != null ) paramOut.print(TAG);
}
//JNI
private native JNIParam doTest(JNIParam paramIn);
}
三、实现JNI的本地实现
#include "JNIParamTest.h"
#include "android/log.h"
#define TAG "JNI_ParamTest"
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
/*
* Class: com_example_jniexample_JNIParamTest
* Method: doTest
* Signature: (Lcom/example/jniexample/JNIParam;)Lcom/example/jniexample/JNIParam;
*/
JNIEXPORT jobject JNICALL Java_com_example_jniexample_JNIParamTest_doTest
(JNIEnv *env, jobject thiz, jobject paramIn)
{
jclass paramInClass = (*env)->GetObjectClass(env, paramIn);
if( paramInClass){
jboolean iscopy;
jfieldID intId = (*env)->GetFieldID(env, paramInClass, "mInt", "I");
jint num = (int)(*env)->GetIntField(env, paramIn, intId);
LOGD("num = %d", num);
jfieldID strId = (*env)->GetFieldID(env, paramInClass, "mString", "Ljava/lang/String;");
jstring str = (jstring)(*env)->GetObjectField(env, paramIn, strId);
const char *locstr = (*env)->GetStringUTFChars(env, str, &iscopy);
LOGD("str = %s", locstr);
(*env)->ReleaseStringUTFChars(env, str, locstr);
}
jclass cls = (*env)->FindClass(env, "com/example/jniexample/JNIParam");
jmethodID id = (*env)->GetMethodID(env, cls, "<init>", "()V");
jobject paramOut = (*env)->NewObjectA(env, cls, id, 0);
jfieldID intId = (*env)->GetFieldID(env, cls, "mInt", "I");
(*env)->SetIntField(env, paramOut, intId, 1);
jfieldID strId = (*env)->GetFieldID(env, cls, "mString", "Ljava/lang/String;");
(*env)->SetObjectField(env, paramOut, strId, (jstring)(*env)->NewStringUTF(env, "1"));
return paramOut;
}