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. setTimeout设置为0 为啥不能立马执行

    setTimeout(function(){}, timer) 是指延时执行.第一个参数是回调函数,第二个参数是指延时多久执行回调函数. setTimeout(function(){console.l ...

  2. IDEA 安装与破解(亲测有效)

    本文转载:https://blog.csdn.net/g_blue_wind/article/details/74380483 根据以下的流程,顺利安装了最新版本的idea企业版. IDEA 全称 I ...

  3. 查看nginx服务器状态

    编译安装时使用--with-http_stub_status_module开启状态页面模块 [root@proxy ~]# yum -y install gcc pcre-devel openssl- ...

  4. nginx反向代理tomcat 时,出现https redirect后变成http的问题解决方法

    需要修改两个配置 1.nginx配置 location / { proxy_pass http://test-server; proxy_set_header Host $host; proxy_se ...

  5. Luogu P1450 [HAOI2008]硬币购物 背包+容斥原理

    考虑如果没有个数的限制,那么就是一个完全背包,所以先跑一个完全背包,求出没有个数限制的方案数即可. 因为有个数的限制,所以容斥一下:没有1个超过限制的方案=至少0个超过限制-至少1个超过限制+至少2个 ...

  6. NetMQ介绍

    NetMQ 是  ZeroMQ的C#移植版本. 一.ZeroMQ ZeroMQ(Ø)是一个轻量级的消息内核,它是对标准socket接口的扩展.它提供了一种异步消息队列,多消息模式,消息过滤(订阅),对 ...

  7. 【java设计模式】-02工厂模式

    工厂模式简述 工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 在工厂模式中,我们在创建对象时不会对客 ...

  8. Python之python简介

    一.Python的优缺点 优点: 1.Python的定位是“优雅”.“明确”.“简单”,所以Python程序看上去总是简单易懂,初学者学Python,不但入门容易,而且将来深入下去,可以编写那些非常非 ...

  9. Docker容器常用命令汇总

    Docker常用命令总结如下: # 查看docker详细信息 docker info # 获取当前节点所有容器 docker ps -a # 管理容器生命周期 docker [start|stop|r ...

  10. 使用 suspend 和 resume 暂停和恢复线程

    suspend 和 resume 的使用 在 Thread 类中有这样两个方法:suspend 和 resume,这两个方法是成对出现的. suspend() 方法的作用是将一个线程挂起(暂停), r ...