JNI实现了C/C++与Java的相互访问,那么这篇文章就从C/C++访问Java开始说起

native函数说明

每个native函数,都至少有两个参数(JNIEnv *jclassjobject)

  • 当native方法为静态方法时,采用jclass,此时jclass代表native方法所属类的class对象
  • 当native方法为非静态时,使用jobject,此时jobject代表native方法所属对象

JNI数据类型

基本数据类型

Java的基本数据类型与JNI数据类型成映射关系

Java类型 <=> JNI类型 <=> C类型

Java Language Type Native Type Description
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits
void void N/A

引用数据类型

Java的引用类型与JNI的对应关系

Java引用类型 JNI类型
String jstring
Object jobject
byte[] jByteArray
int[] jIntArray
String[] jobjectArray
Object[] jobjectArray

值得注意的是:普通数据类型的数组,其在JNI中的表现类似,表格中列举出两个,字符串数组属于Object数组,其表现形式一样

签名

Java Type Type Signature
boolean Z
byte B
char C
short S
int I
long J
float F
double D
void V
fully-qualified-class L fully-qualified-class;
type[] [ type
method type (arg-types) ret-type

说明:

Object:L开头,然后以/分隔包的完整类型,后面再加;,比如String签名就是Ljava/lang/String;

Array:以[开头,再加上数组元素类型的签名,比如int[]签名就是[I,再比如int[][]的签名就是[[I,Object数组的签名就是[Ljava/lang/Object;

使用javap -s -p 完整类名可得到所有签名,需要在bin目录下

调用Java属性

访问非静态属性

在Java中存在

private String key = "jack";
public native String accessFiled(); //触发Java访问C/C++使其在底层修改并返回

非静态属性先得到class,再对其进行操作

Get和Set都有规律可循,GetField和SetField

JNIEXPORT jstring JNICALL Java_com_cj5785_jni_JniTest_accessField
(JNIEnv *env, jobject jobj)
{
//获取到JniTest.class
jclass cls = (*env)->GetObjectClass(env, jobj);
//属性名称,属性签名
jfieldID fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
//获取key属性的值
jstring jstr = (*env)->GetObjectField(env, jobj, fid);
//jstring转化为C的字符串
char *c_str = (char *)(*env)->GetStringUTFChars(env, jstr, NULL);
//C语言处理:字符串拼接
char text[20] = "super ";
strcat(text, c_str);
//将C的字符串转化为jstring
jstring new_string = (*env)->NewStringUTF(env, text);
//修改key
(*env)->SetObjectField(env, jobj, fid, new_string);
//释放资源
(*env)->ReleaseStringUTFChars(env, jstr, c_str);
return new_string;
}

在Java中调用

package com.cj5785.jni;

public class JniTest {

	static {
System.loadLibrary("JNITest");
} private String key = "test"; public native String accessField(); public static void main(String[] args) {
JniTest t = new JniTest();
System.out.println("修改前:" + t.key);
t.accessField();
System.out.println("修改后:" + t.key);
}
}

访问静态属性

在Java中存在

public static int count = 1;
public native void accessStaticField();

在native函数中修改

JNIEXPORT void JNICALL Java_com_cj5785_jni_JniTest_accessStaticField
(JNIEnv *env, jobject jobj)
{
jclass cls = (*env)->GetObjectClass(env, jobj);
jfieldID fid = (*env)->GetStaticFieldID(env, cls, "count", "I");
jint count = (*env)->GetStaticIntField(env, cls, fid);
count++;
(*env)->SetStaticIntField(env, cls, fid, count);
}

在Java中访问

package com.cj5785.jni;

public class JniTest {

	static {
System.loadLibrary("JNITest");
} public static int count = 1; public native void accessStaticField(); public static void main(String[] args) {
JniTest t = new JniTest();
System.out.println("修改前:" + JniTest.count);
t.accessStaticField();
System.out.println("修改后:" + JniTest.count);
}
}

访问属性总结

  • 如果为非静态属性,经历以下步骤

    • GetObjectClass
    • GetFieldID
    • Get<Type>Field
    • 中间处理过程
    • Set<Type>Field
  • 如果为静态属性,经历以下步骤

    • GetObjectClass
    • GetStaticFieldID
    • GetStatic<Type>Field
    • 中间处理过程
    • SetStatic<Type>Field

调用Java方法

访问非静态方法

Java中存在

public native void accessMethod();
public int getRandomInt(int max) {
System.out.println("···getRandomInt run···");
return new Random().nextInt(max);
}

在native中调用

JNIEXPORT void JNICALL Java_com_cj5785_jni_JniTest_accessMethod
(JNIEnv *env, jobject jobj)
{
//jclass
jclass cls = (*env)->GetObjectClass(env, jobj);
//jmethodID
jmethodID mid = (*env)->GetMethodID(env, cls, "getRandomInt", "(I)I");
//Call<Type>Method
jint random = (*env)->CallIntMethod(env, jobj, mid, 100);
printf("%ld\n", random);
}

在Java中触发

package com.cj5785.jni;

import java.util.Random;

public class JniTest {

	static {
System.loadLibrary("JNITest");
} public native void accessMethod(); public int getRandomInt(int max) {
System.out.println("···getRandomInt run···");
return new Random().nextInt(max);
} public static void main(String[] args) {
JniTest t = new JniTest();
t.accessMethod();
}
}

访问静态方法

Java中存在

public native void accessStaticMethod();
public static String getUUID() {
System.out.println("···getUUID run···");
return UUID.randomUUID().toString();
}

在native中调用

JNIEXPORT void JNICALL Java_com_cj5785_jni_JniTest_accessStaticMethod
(JNIEnv *env, jobject jobj)
{
jclass cls = (*env)->GetObjectClass(env, jobj);
jmethodID mid = (*env)->GetStaticMethodID(env, cls, "getUUID", "()Ljava/lang/String;");
jstring jstr = (*env)->CallStaticObjectMethod(env, cls, mid);
char *uuid_str = (*env)->GetStringUTFChars(env, jstr, NULL);
printf("%s\n", uuid_str);
(*env)->ReleaseStringUTFChars(env, jstr, uuid_str);
}

在Java中触发

package com.cj5785.jni;

import java.util.UUID;

public class JniTest {

	static {
System.loadLibrary("JNITest");
} public native void accessStaticMethod(); public static String getUUID() {
System.out.println("···getUUID run···");
return UUID.randomUUID().toString();
} public static void main(String[] args) {
JniTest t = new JniTest();
t.accessStaticMethod();
}
}

访问方法总结

  • 如果为非静态方法,经历以下步骤

    • GetObjectClass
    • GetMethodID
    • Call<Type>Method
    • 处理过程
  • 如果为静态属性,经历以下步骤

    • GetObjectClass
    • GetStaticMethodID
    • GetStatic<Type>Method
    • 处理过程

访问构造方法

使用Date类的getTime()方法,产生当前时间戳

在native中调用Date的getTime方法

JNIEXPORT jobject JNICALL Java_com_cj5785_jni_JniTest_accessConstructor
(JNIEnv *env, jobject jobj)
{
jclass cls = (*env)->FindClass(env, "java/util/Date");
jmethodID construcyor_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
jobject date_obj = (*env)->NewObject(env, cls, construcyor_mid);
jmethodID mid = (*env)->GetMethodID(env, cls, "getTime", "()J");
jlong time = (*env)->CallLongMethod(env, date_obj, mid);
printf("%lld\n", time);
return date_obj;
}

在Java中触发

package com.cj5785.jni;

import java.util.Date;

public class JniTest {

	static {
System.loadLibrary("JNITest");
} public native Date accessConstructor(); public static void main(String[] args) {
JniTest t = new JniTest();
t.accessConstructor();
}
}

访问构造方法,分成以下几个步骤

  • FindClass
  • GetMethodID:初始化
  • NewObject
  • GetMethodID
  • CallMethod

调用父类方法

在Java中存在Person和Student两个类

public class Person {
public void say() {
System.out.println("Person Class");
}
}
public class Student extends Person {
@Override
public void say() {
System.out.println("Student Class");
}
}

在native中调用子类方法,获取父类方法

JNIEXPORT void JNICALL Java_com_cj5785_jni_JniTest_accessNonvirtualMethod
(JNIEnv *env, jobject jobj)
{
jclass cls = (*env)->GetObjectClass(env, jobj);
jfieldID fid = (*env)->GetFieldID(env, cls, "person", "Lcom/cj5785/jni/Person;");
jobject person_obj = (*env)->GetObjectField(env, jobj, fid);
jclass person_cls = (*env)->FindClass(env, "com/cj5785/jni/Person");
jmethodID mid = (*env)->GetMethodID(env, person_cls, "say", "()V");
//执行子类方法
(*env)->CallObjectMethod(env, person_obj, mid);
//执行父类方法
(*env)->CallNonvirtualObjectMethod(env, person_obj, person_cls, mid);
}

在Java中触发

package com.cj5785.jni;

import java.util.Date;
import java.util.Random;
import java.util.UUID; public class JniTest { static {
System.loadLibrary("JNITest");
} public Person person = new Student(); public native void accessNonvirtualMethod(); public static void main(String[] args) {
JniTest t = new JniTest();
t.accessNonvirtualMethod();
}
}

调用父类方法步骤

  • GetObjectClass:获取class对象
  • GetFieldID:获取属性(对象)
  • GetField:获取
  • FindClass:查找父类
  • GetMethodID:获取方法
  • CallMethod(子类方法)或CallNonvirtualMethod(父类方法)

字符串乱码问题

在Java存在

public native String chineseChar(String str);

在native中产生的字符串,当返回时可能会产生乱码问题,这是由于编码格式不同造成的

在Java中传入字符串,那么在native如果不处理,直接返回,那么不会出现乱码

JNIEXPORT jstring JNICALL Java_com_cj5785_jni_JniTest_chineseChar
(JNIEnv *env, jobject jobj, jstring jstr)
{
//使用GetStringUTFChars返回
char *c_str = (*env)->GetStringUTFChars(env, jstr, NULL);
return (*env)->NewStringUTF(env, c_str);
}

但如果对其进行过处理,那么返回的中文字符则会出现乱码问题

以下两例,一个是在输入的字符串做了追加字符,一个是做了新字符串返回,都存在中文,返回的结果都出现了乱码

JNIEXPORT jstring JNICALL Java_com_cj5785_jni_JniTest_chineseChar
(JNIEnv *env, jobject jobj, jstring jstr)
{
//使用GetStringUTFChars返回
//char *c_str = (*env)->GetStringUTFChars(env, jstr, NULL);
//strcat(c_str, "追加");
//return (*env)->NewStringUTF(env, c_str); //使用C转为jstring,然后返回
char *c_str = "native:中文测试";
return (*env)->NewStringUTF(env, c_str);
}

这种情况,有两种解决办法,一种是在C中寻找字符串转码的工具,另一种是直接调用Java的转码工具,前者难度较大,在这里采用后者

JNIEXPORT jstring JNICALL Java_com_cj5785_jni_JniTest_chineseChar
(JNIEnv *env, jobject jobj, jstring jstr)
{
//使用Java的字符串转码工具
char *c_str = "native:中文测试";
//获取jmethod
jclass strcls = (*env)->FindClass(env, "java/lang/String");
jmethodID constructor_mid = (*env)->GetMethodID(env, strcls, "<init>", "([BLjava/lang/String;)V");
//C数组转JNI数组
jbyteArray bytes = (*env)->NewByteArray(env, strlen(c_str));
//数组赋值
(*env)->SetByteArrayRegion(env, bytes, 0, strlen(c_str), c_str);
//设置字符编码
jstring charsetName = (*env)->NewStringUTF(env, "GB2312");
//调用构造方法,返回编码后的jstring
return (*env)->NewObject(env, strcls, constructor_mid, bytes, charsetName);
}

在Java中触发

package com.cj5785.jni;

public class JniTest {

	static {
System.loadLibrary("JNITest");
} public native String chineseChar(String str); public static void main(String[] args) {
JniTest t = new JniTest();
System.out.println(t.chineseChar("中文测试"));
}
}

传入数组的处理

传入int数组,并对其排序

Java中存在native方法

public native void sortArray(int[] array);

在native函数中处理

int compare(int *a, int *b)
{
return (*a - *b);
} JNIEXPORT void JNICALL Java_com_cj5785_jni_JniTest_sortArray
(JNIEnv *env, jobject jobj, jintArray array)
{
//jintArray转化为C int数组
jint *elems = (*env)->GetIntArrayElements(env, array, NULL);
//获取数组长度
int len = (*env)->GetArrayLength(env, array);
//排序
qsort(elems, len, sizeof(jint), compare);
//刷新数组
(*env)->ReleaseIntArrayElements(env, array, elems, JNI_COMMIT);
}

关于数组刷新的同步问题

mode 更新Java数组 释放C/C++数组
`0` `√` `√`
`JNI_ABORT` `×` `√`
`JNI_COMMIT` `√` `×`

在Java中调用
```java
package com.cj5785.jni;

import java.util.Arrays;

public class JniTest {

static {
System.loadLibrary("JNITest");
} public native void sortArray(int[] array); public static void main(String[] args) {
JniTest t = new JniTest();
int[] arr = {5,12,3,6,9,25,1};
System.out.print("排序前:" + Arrays.toString(arr) + "\n");
t.sortArray(arr);
System.out.print("排序后:" + Arrays.toString(arr) + "\n");
}

}


#### 返回数组
Java中存在native方法
```java
public native int[] getArray(int len);

在native中生成数组

JNIEXPORT jintArray JNICALL Java_com_cj5785_jni_JniTest_getArray
(JNIEnv *env, jobject jobj, jint len)
{
//生成jint数组
jintArray jint_arr = (*env)->NewIntArray(env, len);
//获取数组元素
jint *elems = (*env)->GetIntArrayElements(env, jint_arr, NULL);
//为数组赋值
int i = 0;
for (; i < len; i++)
{
elems[i] = i;
}
//同步数组
(*env)->ReleaseIntArrayElements(env, jint_arr, elems, 0);
//返回生成的数组
return jint_arr;
}

在Java中调用生成数组的方法

package com.cj5785.jni;

import java.util.Arrays;

public class JniTest {

	static {
System.loadLibrary("JNITest");
} public native int[] getArray(int len); public static void main(String[] args) {
JniTest t = new JniTest();
int newArr[] = t.getArray(10);
System.out.println(Arrays.toString(newArr));
}
}

NDK学习笔记-JNI数据类型和属性方法的访问的更多相关文章

  1. NDK学习笔记-JNI多线程

    前面讲到记录到ffmpeg音视频解码的时候,采用的是在主线程中进行操作,这样是不行的,在学习了POSIX多线程操作以后,就可以实现其在子线程中解码了,也可以实现音视频同步了 简单示例 在native实 ...

  2. NDK学习笔记-JNI的异常处理与缓存策略

    在使用JNI的时候,可能会产生异常,此时就需要对异常进行处理 异常处理 JNI抛出Throwable异常,在Java层可以用Throwable捕捉 而在C只有清空异常这种处理 但如果在JNI中通过Th ...

  3. NDK学习笔记-JNI开发流程

    JNI(Java Native Interface)Java本地化接口,Java调用C/C++,C/C++调用Java的一套API接口 实现步骤 在Java源文件中编写native方法 public ...

  4. NDK学习笔记-JNI的引用

    JNI中的引用意在告知虚拟机何时回收一个JNI变量 JNI引用变量分为局部引用和全局引用 局部引用 局部引用,通过DeletLocalRef手动释放对象 原因 访问一个很大的Java对象,使用之后还用 ...

  5. Android JNI和NDK学习(06)--JNI的数据类型(转)

    本文转自:http://www.cnblogs.com/skywang12345/archive/2013/05/23/3094037.html 本文介绍JNI的数据类型.NDK中关于JNI数据类型的 ...

  6. SQL反模式学习笔记6 支持可变属性【实体-属性-值】

    目标:支持可变属性 反模式:使用泛型属性表.这种设计成为实体-属性-值(EAV),也可叫做开放架构.名-值对. 优点:通过增加一张额外的表,可以有以下好处 (1)表中的列很少: (2)新增属性时,不需 ...

  7. Java程序猿的JavaScript学习笔记(9—— jQuery工具方法)

    计划按例如以下顺序完毕这篇笔记: Java程序猿的JavaScript学习笔记(1--理念) Java程序猿的JavaScript学习笔记(2--属性复制和继承) Java程序猿的JavaScript ...

  8. 前端学习笔记汇总(之merge方法)

    学习笔记 关于Jquery的merge方法 话不多说,先上图 使用jquery时,其智能提示如上,大概意思就是合并first和second两个数组,得到的结果是first+(second去重后的结果) ...

  9. 《深入Java虚拟机学习笔记》- 第19章 方法的调用与返回

    <深入Java虚拟机学习笔记>- 第19章 方法的调用与返回

随机推荐

  1. C10K问题和多进程模型

    收录编辑来自马哥教育相关课程 内核空间的相关程序在调度用户空间里的进程的时候,也占用了cpu资源...... nginx可以作为两种类型的反向代理 http 和smtp(mail) C10K问题, 当 ...

  2. jmeter结果树乱码的解决方案

  3. 移动端H5开发问题记录

    1. 当弹出键盘时,会改变页面高度,影响页面样式 通过window.onsize事件可以控制键盘弹出或消失的时候的样式 var h = document.body.scrollHeight // 用o ...

  4. BZOJ 2100: [Usaco2010 Dec]Apple Delivery spfa

    由于是无向图,所以可以枚举两个终点,跑两次最短路来更新答案. #include <queue> #include <cstdio> #include <cstring&g ...

  5. 《30天自制操作系统》学习笔记--Mac环境搭建

    弄了三天了,终于弄好了,先说结果,就是作者在网站上放了os x的工具(hrb.osask.jp,也有linux下的工具,可以自己去下载),也就是说我白忙活了三天... 再说一下这几天都干啥了,主要是想 ...

  6. 树莓派安装QT(全部库包括)

    在网上现有的资料中大部分只有前两个命令,少量有三个命令,因此写下该博客 在树莓派上安装QT5的全部库,包括QtQuick.QtMultimedia库. sudo apt-get install qt5 ...

  7. 用sql语句查询一列名中的各个数值的个数

    SELECT COUNT(case when f.fileState=2 then 0 end) as fixed,COUNT(case when f.fileState=3 then 0 end) ...

  8. python中with语句的使用

    引言 with 语句是从 Python 2.5 开始引入的一种与异常处理相关的功能(2.5 版本中要通过 from __future__ import with_statement 导入后才可以使用) ...

  9. 【redis 学习系列07】Redis小功能大用处01 慢查询分析以及Redis Shell

    Redis提供了5种数据结构已经足够强大,但除此之外,Redis还提供了诸如慢查询分析.功能强大的Redis Shell.Pipeline.事务与Lua脚本.Bitmaps.HyperLogLog.发 ...

  10. docker安装redis,并用配置启动

    1.拉取redis镜像 docker pull redis 2.创建redis本地配置文件 ①.去redis官网下载redis,获取redis.conf文件 ②.修改redis.conf文件相关配置, ...