JNI 自定义类型参数和返回值 例子

  • Post author:
  • Post category:其他


在使用 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;  
}  



版权声明:本文为hu626626原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。