Android JNI 数组操作_weixin_30241919的博客-程序员宅基地

技术标签: java  移动开发  c/c++  

JNI 中有两种数组操作,基础数据类型数组和对象数组,JNI 对待基础数据类型数组和对象数组是不一样的。

基本数据类型数组

对于基本数据类型数组,JNI 都有和 Java 相对应的结构,在使用起来和基本数据类型的使用类似。

在 Android JNI 基础知识篇提到了 Java 数组类型对应的 JNI 数组类型。比如,Java int 数组对应了 jintArray,boolean 数组对应了 jbooleanArray。

如同 String 的操作一样,JNI 提供了对应的转换函数:GetArrayElements、ReleaseArrayElements。

    intArray = env->GetIntArrayElements(intArray_, NULL);
    env->ReleaseIntArrayElements(intArray_, intArray, 0);

另外,JNI 还提供了如下的函数:

  • GetTypeArrayRegion / SetTypeArrayRegion

将数组内容复制到 C 缓冲区内,或将缓冲区内的内容复制到数组上。

  • GetArrayLength

得到数组中的元素个数,也就是长度。

  • NewTypeArray

返回一个指定数据类型的数组,并且通过 SetTypeArrayRegion 来给指定类型数组赋值。

  • GetPrimitiveArrayCritical / ReleasePrimitiveArrayCritical

如同 String 中的操作一样,返回一个指定基础数据类型数组的直接指针,在这两个操作之间不能做任何阻塞的操作。

实际操作如下:

    // Java 传递 数组 到 Native 进行数组求和
    private native int intArraySum(int[] intArray, int size); 

对应的 C++ 代码如下:

JNIEXPORT jint JNICALL
Java_com_glumes_cppso_jnioperations_ArrayTypeOps_intArraySum(JNIEnv *env, jobject instance,
                                                             jintArray intArray_, jint num) {
    jint *intArray;
    int sum = 0; // 操作方法一: // 如同 getUTFString 一样,会申请 native 内存 intArray = env->GetIntArrayElements(intArray_, NULL); if (intArray == NULL) { return 0; } // 得到数组的长度 int length = env->GetArrayLength(intArray_); LOGD("array length is %d", length); for (int i = 0; i < length; ++i) { sum += intArray[i]; } LOGD("sum is %d", sum); // 操作方法二: jint buf[num]; // 通过 GetIntArrayRegion 方法来获取数组内容 env->GetIntArrayRegion(intArray_, 0, num, buf); sum = 0; for (int i = 0; i < num; ++i) { sum += buf[i]; } LOGD("sum is %d", sum); // 使用完了别忘了释放内存 env->ReleaseIntArrayElements(intArray_, intArray, 0); return sum; } 

假如需要从 JNI 中返回一个基础数据类型的数组,对应的代码如下:

    // 从 Native 返回基本数据类型数组
    private native int[] getIntArray(int num); 

对应的 C++ 代码如下:

/**
 * 从 Native 返回 int 数组,主要调用 set<Type>ArrayRegion 来填充数据,其他数据类型类似操作
 */
extern "C"
JNIEXPORT jintArray JNICALL Java_com_glumes_cppso_jnioperations_ArrayTypeOps_getIntArray(JNIEnv *env, jobject instance, jint num) { jintArray intArray; intArray = env->NewIntArray(num); jint buf[num]; for (int i = 0; i < num; ++i) { buf[i] = i * 2; } // 使用 setIntArrayRegion 来赋值 env->SetIntArrayRegion(intArray, 0, num, buf); return intArray; } 

以上例子,基本把相关的操作都使用上了,可以发现和 String 的操作大都是相似的。

对象数组

对于对象数组,也就是引用类型数组,数组中的每个类型都是引用类型,JNI 只提供了如下函数来操作。

  • GetObjectArrayElement / SetObjectArrayElement

和基本数据类型不同的是,不能一次得到数据中的所有对象元素或者一次复制多个对象元素到缓冲区。只能通过上面的函数来访问或者修改指定位置的元素内容。

字符串和数组都是引用类型,因此也只能通过上面的方法来访问。

例如在 JNI 中创建一个二维的整型数组并返回:

    // 从 Native 返回二维整型数组,相当于是一个一维整型数组,数组中的每一项内容又是数组
    private native int[][] getTwoDimensionalArray(int size); 

二维数组具有特殊性在于,可以将它看成一维数组,其中数组的每项内容又是一维数组。

具体 C++ 代码如下:

/**
 * 从 Native 返回一个二维的整型数组
 */
extern "C"
JNIEXPORT jobjectArray JNICALL Java_com_glumes_cppso_jnioperations_ArrayTypeOps_getTwoDimensionalArray(JNIEnv *env, jobject instance, jint size) { // 声明一个对象数组 jobjectArray result; // 找到对象数组中具体的对象类型,[I 指的就是数组类型 jclass intArrayCls = env->FindClass("[I"); if (intArrayCls == NULL) { return NULL; } // 相当于初始化一个对象数组,用指定的对象类型 result = env->NewObjectArray(size, intArrayCls, NULL); if (result == NULL) { return NULL; } for (int i = 0; i < size; ++i) { // 用来给整型数组填充数据的缓冲区 jint tmp[256]; // 声明一个整型数组 jintArray iarr = env->NewIntArray(size); if (iarr == NULL) { return NULL; } for (int j = 0; j < size; ++j) { tmp[j] = i + j; } // 给整型数组填充数据 env->SetIntArrayRegion(iarr, 0, size, tmp); // 给对象数组指定位置填充数据,这个数据就是一个一维整型数组 env->SetObjectArrayElement(result, i, iarr); // 释放局部引用 env->DeleteLocalRef(iarr); } return result; } 

首先需要使用 NewObjectArray 方法来创建对象数组。

然后使用 SetObjectArrayElement 函数填充数据时,需要构建好每个位置对应的对象。这里就使用了 NewIntArray 来创造了一个对象,并给对象填充数据后,在赋值给对象数组。

通过一个 for 循环就完成给对象数组赋值的操作。

在创建对象数组时,有一个操作是找到对应的对象类型,通过 findClass 方法。findClass 的参数 [I 这里就涉及到 Java 与 JNI 对应签名的转换。

Java 与 JNI 签名的转换

在前一篇文章中,用表格列出了 Java 与 JNI 对应的数据类型格式的转换关系,现在要列举的是 Java 与 JNI 对应签名的转换关系。

这里的签名指的是在 JNI 中去查找 Java 中对应的数据类型、对应的方法时,需要将 Java 中的签名转换成 JNI 所能识别的。

对于类的签名转换

对于 Java 中类或者接口的转换,需要用到 Java 中类或者接口的全限定名,把 Java 中描述类或者接口的 . 换成 / 就好了,比如 String 类型对应的 JNI 描述为:

java/lang/String     // . 换成 / 

对于数组类型,则是用 [ 来表示数组,然后跟一个字段的签名转换。

[I      // 代表一维整型数组,I 表示整型
[[I     // 代表二维整型数组
[Ljava/lang/String;      // 代表一维字符串数组, 
对于字段的签名转换

对应基础类型字段的转换:

Java 类型 JNI 对应的描述转
boolean Z
byte B
char C
short S
int I
long J
float F
double D

对于引用类型的字段签名转换,是大写字母 L 开头,然后是类的签名转换,最后以 ; 结尾。

Java 类型 JNI 对应的描述转换
String Ljava/lang/String;
Class Ljava/lang/Class;
Throwable Ljava/lang/Throwable
int[] "[I"
Object[] "[Ljava/lang/Object;"
对于方法的签名转换

对于方法签名描述的转换,首先是将方法内所有参数转换成对应的字段描述,并全部写在小括号内,然后在小括号外再紧跟方法的返回值类型描述。

Java 类型 JNI 对应的描述转换
String f(); ()Ljava/lang/String;
long f(int i, Class c); (ILjava/lang/Class;)J
String(byte[] bytes); ([B)V

这里要注意的是在 JNI 对应的描述转换中不要出现空格。

了解并掌握这些转换后,就可以进行更多的操作了,实现 Java 与 C++ 的相互调用。

比如,有一个自定义的 Java 类,然后再 Native 中打印类的对象数组的某一个字段值。

    private native void printAnimalsName(Animal[] animal); 

具体 C++ 代码如下:

/**
 * 打印对象数组中的信息
 */
extern "C"
JNIEXPORT void JNICALL Java_com_glumes_cppso_jnioperations_ArrayTypeOps_printAnimalsName(JNIEnv *env, jobject instance, jobjectArray animals) { jobject animal; // 数组长度 int size = env->GetArrayLength(animals); // 数组中对应的类 jclass cls = env->FindClass("com/glumes/cppso/model/Animal"); // 类对应的字段描述 jfieldID fid = env->GetFieldID(cls, "name", "Ljava/lang/String;"); // 类的字段具体的值 jstring jstr; // 类字段具体值转换成 C/C++ 字符串 const char *str; for (int i = 0; i < size; ++i) { // 得到数组中的每一个元素 animal = env->GetObjectArrayElement(animals, i); // 每一个元素具体字段的值 jstr = (jstring) (env->GetObjectField(animal, fid)); str = env->GetStringUTFChars(jstr, NULL); if (str == NULL) { continue; } LOGD("str is %s", str); env->ReleaseStringUTFChars(jstr, str); } } 

具体示例代码可参考我的 Github 项目,欢迎 Star。

https://github.com/glumes/AndroidDevWithCpp

转载于:https://www.cnblogs.com/Free-Thinker/p/10602008.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_30241919/article/details/99071743

智能推荐

AD域控exchange邮箱—powershell 程序暂停sleep 继续执行的方法_罗四强的博客-程序员宅基地_powershell sleep

暂停Windows PowerShell 10秒:Start-Sleep –s 10暂停脚本10秒(10,000毫秒)Start-Sleep –m 10000语法Start-Sleep [-seconds] &lt;int&gt; [&lt;CommonParameters&gt;]Start-Sleep -milliseconds &lt;int&gt; [&lt;CommonParameters&gt;]详细描述Start-Sleep cmdlet使s...

python画损失函数图_深度学习笔记48_你也可以成为梵高_损失函数算法实践_weixin_39929096的博客-程序员宅基地

from keras import backend as K# constant变量参数来定义target_image = K.constant(preprocess_image(target_image_path))style_reference_image = K.constant(preprocess_image(style_reference_image_path))# 用palcehol...

最小二乘法(Least Square)和最大似然估计_whitenightwu的博客-程序员宅基地_least square

最小二乘法(Least Square)线性最小二乘(OLS,online Least Square)  最小二乘,其实就是最小方差。  找到一个(组)估计值,使得实际值与估计值的距离最小。本来用两者差的绝对值汇总并使之最小是最理想的,但绝对值在数学上求最小值比较麻烦,因而替代做法是,找一个(组)估计值,使得实际值与估计值之差的平方加总之后的值最小,称为最小二乘。“二乘”的英文为least s...

pom java,java父pom_会计星球的博客-程序员宅基地

该楼层疑似违规已被系统折叠隐藏此楼查看此楼xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;4.0.0com.taotao.parenttaotao-parent1.0.0-SNAPSHOTpom4.104.1.3.RELEASE3.2.81....

基于java的心理健康_基于SSM的JAVA心理健康网_小仙女CiCi的博客-程序员宅基地

标签:结果shaXMLtarge时间existsjava响应查询今天记录的项目是心理健康网的规划与设计,这个项目是这么回事:心理健康是关系到人才质量的重要问题。随着网络时代的发展,传统的大学生心理健康教育教学方式和课程资源远不能解决当代大学生的心理问题。本文通过对学生心理健康网的规划与设计进行研究。首先,阐述了学生心理健康教育的现状;其次,阐述了学生...

Multipart: Boundary not found multer上传文件报错解决(node.js)/前端vue上传formData中file为{}空对象解决_渊来有你的博客-程序员宅基地

今天在前端使用vue用formdata进行文件的传输时后台node报错Multipart: Boundary not found multer前端控制台当中发现接口传输的file文件为空值{},因为在axios请求时设置请求头Content-Type为multipart/form-data时发现在后面没有Boundary 这个参数,正常来说除了multipart/form-datamultipart/form-data;boundary :****************后面应该还有一串b

随便推点

java参数初始化_java变量的初始化_吴乎的博客-程序员宅基地

public class Init {private int age;//非静态初始化语句&lt;3&gt;private static String name; //静态初始化语句,先初始化静态 &lt;1.1&gt;/**静态初始化语句和静态初始化模块都是静态(同等级),谁在前面先初始化谁*/{/**动态初始化模块(非静态初始化模块)&lt;4&gt;*/double width;float ...

Netty源码分析(一),Reactor模型,创建Nio线程组的过程_L Y C的博客-程序员宅基地

Reactor 模型Reactor适用于同步io事件分离者等待某个事件或者另外一个操作的状态发生分离者就会将事件传给回调函数处理经典Reactorreactor 将Io事件转发给对应的handleracceptor 处理连接事件Netty的Reactor 模型使用多Reactor模型一个Reactor监控所有的连接请求多个子Reactor监控处理读写请求一个子Reactor属于独立的线程,每个成功连接的Channel的操作只由一个线程处理,避免了不必要的上下文切换。

CVE-2018-3760 Ruby On Rails 路径穿越漏洞_GuiltyFet的博客-程序员宅基地

漏洞简介Ruby On Rails在开发环境下使用Sprockets作为静态文件服务器,Ruby On Rails是著名Ruby Web开发框架,Sprockets是编译及分发静态资源文件的Ruby库。Sprockets 3.7.1及之前版本中,存在一处因为二次解码导致的路径穿越漏洞,攻击者可以利用%252e%252e/来跨越到根目录,读取或执行目标服务器上任意文件。漏洞复现启动一个用Ruby On Rails脚手架生成的默认站点:docker-compose up -dhttp://127.

python yield from yield_python yield 和 yield from用法总结_weixin_39778582的博客-程序员宅基地

#例1. 简单输出斐波那契數列前 N 个数#缺点:该函数可复用性较差,因为 fab 函数返回 None,其他函数无法获得该函数生成的数列#要提高 fab 函数的可复用性,最好不要直接打印出数列,而是返回一个 List。def fab1(max):n, a, b = 0, 0, 1while n &lt; max:print(b,end=‘ ‘)a, b = b, a + bn = n + 1fab...

华为荣耀magic是鸿蒙系统,荣耀Magic3是什么系统-采用什么系统_朕说的博客-程序员宅基地

据网上最新消息荣耀Magic3即将发布,同时这款手机的大部分信息也被曝光,那么这款荣耀新机采用的是什么系统呢,想知道的朋友快来看看吧。荣耀Magic3是什么系统荣耀Magic 3系统采用全新的鸿蒙系统将会上线,基于微内核打造,操作流畅度较高,同时安全性也极高。其他配置方面屏幕方面荣耀Magic 3会沿用之前的八曲面设计,采用一块6.8英寸的动态AMOLED屏幕,分辨率为2K,支持120Hz高刷新率...

C++入门-----引用_小猪-乔治的博客-程序员宅基地

引用概念 引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间 类型&amp; 引用变量名(对象名) = 引用实体;void TestRef(){int a = 10;int&amp; ra = a;//&lt;====定义引用类型printf("%p\n", &amp;a);print...

推荐文章

热门文章

相关标签