JNI 传递和返回基本参数

Java 中的基本类型包括 boolean,byte,char,short,int,long,float,double 这样几种,本文主要介绍上层向底层传递基本类型数据,以及底层向上层返回基本数据类型的情况。 本文地址

一、新建 JNIWrapper 类,用于对 native 接口的声明和封装
如下先建好 JNIWrapper 类,用于对 native 接口的封装,当然也可以直接将 native 接口声明为 public,外面直接调用,或直接在需要调用的类里直接声明。但个人还是比较喜欢将其声明为 private 的,在 native 之上在封装一层 java 层接口,放在在单独的一个 Java 类里,对外只暴露 Java 接口。这样有两个好处:
1、可以 native 方法统一维护和管理;
2、外部调用进来时可以对传入的参数做合法性判断,非法参数不必继续往下层调;
3、设计 native 接口时,如果是传参或关键接口时一般都会设计返回值,返回错误码,用于反馈上层该接口是否调用成功,那么在这个封装类里可以截获返回值,如果底层出错向调用者抛出异常,这样外层只需要捕获异常就好了,不需要判断返回值,更符合 Java 编码风格。一般 Java 外部调用者很少会对一个函数做返回值判断的,调用者可能忽略返回值,从而使得底层已经返回该调用出错了,上层还继续往下调用,导致未知错误。
package com.alan.alanjni;

/**
 * Author: AlanWang.
 * Date: 18/3/26 15:18.
 * Mail: [email protected]
 */

public class JNIWrapper {
    /**
     * 底层的成功码,如果返回码不为该值,说明调用出错
     */
    private static final int SUCCESS_CODE = 0;


    // Used to load the 'alanjni' library on application startup.
    static {
        System.loadLibrary("alanjni");
    }


    /******************************************************************
     *  java 接口,调用底层接口向底层传递基本数据类型
     *****************************************************************/

    /**
     * 调用底层 nativeSetArgBoolean 接口
     * @param bRrg
     * @return
     */
    public boolean setArgBoolean(boolean bRrg) {
        return nativeSetArgBoolean(bRrg);
    }

    /**
     * 调用底层 nativeSetArgByte 接口
     * @param bArg
     * @return
     */
    public byte setArgByte(byte bArg) {
        return nativeSetArgByte(bArg);
    }

    /**
     * 调用底层 nativeSetArgChar 接口
     * @param cArg
     * @return
     */
    public char setArgChar(char cArg) {
        return nativeSetArgChar(cArg);
    }

    /**
     * 调用底层 nativeSetArgShort 接口
     * @param sArg
     * @return
     */
    public short setArgShort(short sArg) {
        return nativeSetArgShort(sArg);
    }

    /**
     * 调用底层 nativeSetArgInt 接口,如果参数错误或执行错误,则向调用者抛出异常
     * @param iArg
     * @throws RuntimeException
     */
    public void setArgInt(int iArg) throws RuntimeException {
        if (iArg < 0) {
            throw new IllegalArgumentException("Invalid argument when call setArgInt.");
        }
        int errCode = nativeSetArgInt(iArg);
        if (SUCCESS_CODE != errCode) {
            throw new RuntimeException("Error during setArgInt.");//一般这里可以是自定义异常
        }
    }

    /**
     * 调用底层 nativeSetArgShort 接口
     * @param lArg
     * @return
     */
    public long setArgLong(long lArg) {
        return nativeSetArgLong(lArg);
    }

    /**
     * 调用底层 nativeSetArgFloat 接口
     * @param fArg
     * @return
     */
    public float setArgFloat(float fArg) {
        return nativeSetArgFloat(fArg);
    }

    /**
     * 调用底层 nativeSetArgDouble 接口
     * @param dArg
     * @return
     */
    public double setArgDouble(double dArg) {
        return nativeSetArgDouble(dArg);
    }



    /******************************************************************
     *  向底层传递基本数据类型,以及底层向上层返回基本数据类型
     *****************************************************************/
    /**
     * 向底层传递 boolean 型参数,并返回 boolean 型返回值
     * @param bArg
     * @return
     */
    private native boolean nativeSetArgBoolean(boolean bArg);

    /**
     * 向底层传递 byte 型参数,并返回 byte 型返回值
     * @param bArg
     * @return
     */
    private native byte nativeSetArgByte(byte bArg);

    /**
     * 向底层传递 char 型参数,并返回 char 型返回值
     * @param cArg
     * @return
     */
    private native char nativeSetArgChar(char cArg);

    /**
     * 向底层传递 short 型参数,并返回 short 型返回值
     * @param sArg
     * @return
     */
    private native short nativeSetArgShort(short sArg);

    /**
     * 向底层传递 int 型参数,并返回 int 型返回值
     * @param iArg
     * @return
     */
    private native int nativeSetArgInt(int iArg);

    /**
     * 向底层传递 long 型参数,并返回 long 型返回值
     * @param lArg
     * @return
     */
    private native long nativeSetArgLong(long lArg);

    /**
     * 向底层传递 float 型参数,并返回 float 型返回值
     * @param fArg
     * @return
     */
    private native float nativeSetArgFloat(float fArg);

    /**
     * 向底层传递 float 型参数,并返回 float 型返回值
     * @param dArg
     * @return
     */
    private native double nativeSetArgDouble(double dArg);

    //////////////////////////////////////////////////////////////////////
}
二、生成 jni 头文件
建好 JNIWrapper 类后需要生成 JNI 方法声明的头文件,这个可以使用 java 命令生成。有两种方案:
1、先进入包的根目录及 com 上级目录,然后执行:javah com.alan.alanjni.JNIWrapper,然后在就好当前执行命令的目录下生成 com_alan_alanjni_JNIWrapper.h 文件;
2、另外一种就是使用javah 命令对 JNIWrapper.class 文件操作,如果编译过,class 文件在项目的 app/build/intermediates/classes/debug/com/alan/alanjni/JNIWrapper.class  可以把这个 JNIWrapper.class 拷贝到任意目录,然后执行 javah  JNIWrapper.class ,就会在该目录下生成 com_alan_alanjni_JNIWrapper.h 文件。刚写好的 JNIWrapper 类是没有 JNIWrapper.class 文件的,在 Android Studio 的工具栏 Build—>Rebuild Project 再去看就有了。

然后把 com_alan_alanjni_JNIWrapper.h 文件拷贝到 cpp 目录下(或你想放的其他目录,只有后面在 CMakeLists 里把路径配置对就好),当然如果嫌文件名太长,可以修改文件名,比如我就改成 JNIWrapper.h 。

三、新建 jni 的实现文件并完成具体实现
新建同名的 .cpp 文件,然后在文件头,include jni.h 和 JNIWrapper.h 文件,然后将刚才的头文件中生成的所有函数复制到 .cpp 中,那么 cpp 目录有两个文件 JNIWrapper.h 和 JNIWrapper.cpp,然后我们需要在 JNIWrapper.cpp 中完成具体实现。如下: 
JNIEXPORT jboolean JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgBoolean(JNIEnv *, jobject, jboolean);
首先我们需要将参数补全,改为如下:
JNIEXPORT jboolean JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgBoolean(JNIEnv *env, jobject obj, jboolean bArg) {
    jboolean ret = !bArg; // 对传入的参数 bArg 取反
    return ret;
}
记得函数声明有返回值的一定要 return 返回值,否则尽管编译过了,但运行的时候会报错崩溃,其他函数也类似,并实现具体逻辑。整体实现代码如下:
#include <android/log.h>
#include "JNIWrapper.h"

#define LOGD(TAG, ...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define TAG_JNI "TAG_JNI"

/*
 * Class:     com_alan_alanjni_JNIWrapper
 * Method:    nativeSetArgBoolean
 * Signature: (Z)Z
 */
JNIEXPORT jboolean JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgBoolean
  (JNIEnv *env, jobject obj, jboolean bArg) {
    jboolean ret = !bArg; // 对传入的参数 bArg 取反
    LOGD(TAG_JNI, "nativeSetArgBoolean()--->bArg = %d, ret = %d", bArg, ret);
    return ret;
}

/*
 * Class:     com_alan_alanjni_JNIWrapper
 * Method:    nativeSetArgByte
 * Signature: (B)B
 */
JNIEXPORT jbyte JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgByte
  (JNIEnv *env, jobject obj, jbyte bArg) {
    jbyte ret = bArg << 2; // 对传入的 bArg 左移两位(即乘 2 )
    LOGD(TAG_JNI, "nativeSetArgByte()--->bArg = %d, ret = %d", bArg, ret);
    return ret;
}

/*
 * Class:     com_alan_alanjni_JNIWrapper
 * Method:    nativeSetArgChar
 * Signature: (C)C
 */
JNIEXPORT jchar JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgChar
  (JNIEnv *env, jobject obj, jchar cArg) {
    jchar ret = cArg + 1; // 对传入的 cArg 加 1,即其下一个字符
    LOGD(TAG_JNI, "nativeSetArgChar()--->cArg = %c, ret = %c", cArg, ret);
    return ret;
}

/*
 * Class:     com_alan_alanjni_JNIWrapper
 * Method:    nativeSetArgShort
 * Signature: (S)S
 */
JNIEXPORT jshort JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgShort
  (JNIEnv *env, jobject obj, jshort sArg) {
    jshort ret = sArg + 2; // 对传入的参数 sArg 加 2
    LOGD(TAG_JNI, "nativeSetArgShort()--->sArg = %d, ret = %d", sArg, ret);
    return ret;
}

/*
 * Class:     com_alan_alanjni_JNIWrapper
 * Method:    nativeSetArgInt
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgInt
  (JNIEnv *env, jobject obj, jint iArg) {
    jint ret;
    if (iArg < 10) {// 如果传入的参数 iArg 小于 10,则返回成功,否则返回失败
        ret = 0;
    } else {
        ret = -1;
    }
    LOGD(TAG_JNI, "nativeSetArgInt()--->iArg = %d, ret = %d", iArg, ret);
    return ret;
}

/*
 * Class:     com_alan_alanjni_JNIWrapper
 * Method:    nativeSetArgLong
 * Signature: (J)J
 */
JNIEXPORT jlong JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgLong
  (JNIEnv *env, jobject obj, jlong lArg) {
    jlong ret = lArg + 1 * 1000 * 1000; // 对传入的 lArg 做运算
    LOGD(TAG_JNI, "nativeSetArgLong()--->lArg = %ld, ret = %ld", lArg, ret);
    return ret;
}

/*
 * Class:     com_alan_alanjni_JNIWrapper
 * Method:    nativeSetArgFloat
 * Signature: (F)D
 */
JNIEXPORT jfloat JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgFloat
  (JNIEnv *env, jobject obj, jfloat fArg) {
    jfloat ret = fArg + 1.0f; // 对传入的 fArg 做运算
    LOGD(TAG_JNI, "nativeSetArgFloat()--->fArg = %f, ret = %f", fArg, ret);
    return ret;
}

/*
 * Class:     com_alan_alanjni_JNIWrapper
 * Method:    nativeSetArgDouble
 * Signature: (D)D
 */
JNIEXPORT jdouble JNICALL Java_com_alan_alanjni_JNIWrapper_nativeSetArgDouble
  (JNIEnv *env, jobject obj, jdouble dArg) {
    jdouble ret = dArg + 2.0; // 对传入的 dArg 做运算
    LOGD(TAG_JNI, "nativeSetArgDouble()--->dArg = %lf, ret = %lf", dArg, ret);
    return ret;
}
四、将文件加入编译,并在上层调用测试
打开 src 目录下的 CMakeLists.txt 文件,然后在 add_libraty 中加入 src/main/cpp/JNIWrapper.cpp,然后在 上层的 java 代码中调用 JNIWrapper 封装的接口进行调用测试。

五、总结
我们可以看到,Java中的基本类型 boolean,byte,char,short,int,long,float,double,在 jin 中分别对应的类型是 jboolean,jbyte,jchar,jshort,jint,jlong,afloat,jdouble,这几种类型都可以当成对应的C++类型来用。Java 和 Native 类型对应关系如下表:

Java 类型 Native 类型 描述
boolean jboolean C/C++8位整型
byte jbyte C/C++带符号的8位整型
char jchar C/C++无符号的16位整型
short jshort C/C++带符号的16位整型
int jint C/C++带符号的32位整型
long jlong C/C++带符号的64位整型e
float jfloat C/C++32位浮点型
double jdouble C/C++64位浮点型
Object jobject 任何Java对象,或者没有对应java类型的对象
Class jclass Class对象
String jstring 字符串对象
Object[] jobjectArray 任何对象的数组
boolean[] jbooleanArray 布尔型数组
byte[] jbyteArray 比特型数组
char[] jcharArray 字符型数组
short[] jshortArray 短整型数组
int[] jintArray 整型数组
long[] jlongArray 长整型数组
float[] jfloatArray 浮点型数组
double[] jdoubleArray 双浮点型数组 
代码见 AlanJNI
Demo 下载地址 (github 会一直更新,下载地址是只包含到本文时的代码)

猜你喜欢

转载自blog.csdn.net/u011520181/article/details/79765336
JNI
今日推荐