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. BZOJ 3439: Kpm的MC密码 (trie+dfs序主席树)

    题意 略 分析 把串倒过来插进trietrietrie上, 那么一个串的kpmkpmkpm串就是这个串在trietrietrie上对应的结点的子树下面的所有字符串. 那么像 BZOJ 3551/354 ...

  2. 05 django组件:contenttype

    1.django组件:contenttype 组件的作用:可以通过两个字段让表和N张表创建FK关系 1.专题课,学位课 如何关联 过期时间?? 方法1:分别创建 专题课--过期时间表 .学位课--过期 ...

  3. [Python自学] day-17 (jQuery)

    一.jQuery简介 参考文档链接:http://jquery.cuishifeng.cn/ jQuery是JS的一个类库,是对DOM.BOM等的封装,方便我们来查找和操作元素. jQuery分为1. ...

  4. plupload+上传文件夹

    文件夹数据库处理逻辑 publicclass DbFolder { JSONObject root; public DbFolder() { this.root = new JSONObject(); ...

  5. CPU结构及段地址偏移地址的概念

    原文地址:http://blog.csdn.net/yihuiworld/article/details/7533335#comments 程序如何执行: CPU先找到程序在内存中的入口地址 -- 地 ...

  6. Mysql 修改最大链接数

    链接数过小经常出现ERROR 1040: Too many connections错误 show variables like '%max_connections%';  查看当前的mysql链接值 ...

  7. python 语音输入

    # 系统客户端包 import win32com.client speaker = win32com.client.Dispatch("SAPI.SPVOICE") # 系统接口 ...

  8. JavaWeb_(Spring框架)在Struts+Hibernate框架中引入Spring框架

    spring的功能:简单来说就是帮我们new对象,什么时候new对象好,什么时候销毁对象. 在MySQL中添加spring数据库,添加user表,并添加一条用户数据 使用struts + hibern ...

  9. ANDROID_ID

    在设备首次启动时,系统会随机生成一个64位的数字,并把这个数字以16进制字符串的形式保存下来,这个16进制的字符串就是ANDROID_ID,当设备被wipe后该值会被重置.可以通过下面的方法获取: i ...

  10. 用java写一个两个任意长度字符串数字和的算法

    package com.cn.test.string; public class StringTest { public static void main(String[] args) { Strin ...