当前位置: 首页 > news >正文

Java中JNI的使用(上)

JNI 全称是 Java Native Interface。是在 Java 和 Native 层(包括但不限于C/C++)相互调用的接口规范。

JNI 在 Java 1.1中正式推出,在 Java 1.2版本中加入了 JNI_OnLoad、JNI_OnUnload 方法,这两个方法还是很有用的,后面再说。

JNI基础篇

Java 通过 JNI 调用本地方法的过程大致是:

写一个 Java 类,在其中声明对应要调用的 native 方法,用 native 关键字修饰。 比如 private static native int native_newInstance();
通过 javah 命令生成 Java 类对应的 C/C++ 头文件。javah -encoding utf-8 -cp src com.young.soundtouch.SoundTouch;
在 C/C++ 中实现头文件中声明的函数;
编译 C/C++ 代码为动态库(Windows中的dll、Linux/Android 中的 so、MAC OSX 中的 dylib);
在 Java 代码中加载动态库,即可像调用 Java 方法一样,调用到 native 函数。
其中第3步在 Java 1.2 中增加了 JNI_OnLoad 方法之后有另一种实现方式(后面说)。

javah 生成的头文件大致是这样的:

/ DO NOT EDIT THIS FILE - it is machine generated /
#include <jni.h>
/ Header for class com_young_soundtouch_SoundTouch /

#ifndef _Included_com_young_soundtouch_SoundTouch
#define _Included_com_young_soundtouch_SoundTouch
#ifdef __cplusplus
extern "C" {
#endif
#undef com_young_soundtouch_SoundTouch_SETTING_USE_AA_FILTER
#define com_young_soundtouch_SoundTouch_SETTING_USE_AA_FILTER 0L
/*

  • Class: com_young_soundtouch_SoundTouch
  • Method: native_getDefaultSampleElementSize
  • Signature: ()I
    /
    JNIEXPORT jint JNICALL Java_com_young_soundtouch_SoundTouch_native_1getDefaultSampleElementSize
    (JNIEnv
    , jclass);
    #ifdef __cplusplus
    }
    #endif
    #endif
    文件开头就是普通的头文件,但是可以发现:

包含了 jni.h 头文件(一般位于 $JAVA_HOME/jd{jdk-version}/include 文目录内)。这是 JNI 中所有的类型、函数、宏等定义的地方。所以C/C++世界的JNI是由他制定的游戏规则。
在类中生命的常量(static final)类型会在头文件中以宏的形式出现,这一点还是很方便的。
函数的注释还是比较全的,包括了:
对应的 class
对应的 Java 方法名
对应 Java 方法的签名
方法的声明显得有点奇怪,由以下及部分组成:
JNIEXPORT这是函数的导出方式;
jint 返回值类型(jint 由 jni.h 定义,对应 int,下面具体再说吧);
JNICALL 函数的调用方式也就是汇编级别参数的传入方式;
Java_com_young_soundtouch_SoundTouch_native1getDefaultSampleElementSize —— 超级长的函数名!!!格式是 Java + 类全名 + + JAVA 中声明的native方法名。其中会把包名中的点(.)替换成下划线(),同时为了避免冲突把下划线替换成_1;
方法的参数,上面的这个方法在 Java 的声明中实际上是没有参数的,其中的 JNIENV 顾名思义是 JNI 环境,和具体的线程绑定。而第二个参数 jclass 其实是 Java 中的 Class。因为上面是一个 static 方法,因此第二个参数是 jclass。如果是一个实例方法则对应第二个参数是 jobject,相当于 Java中的 this。
下面在 C/C++ 中实现这个方法就行啦。但是在动手前现大致了解以下 jni.h 制定的游戏规则。

类型转换

javah 生成的头文件里面使用的类型都是 jni.h 定义的,目的是做到平台无关,比如保证在所有平台上 jint 都是32位的有符号整型。

基本对应关系如下:

引用类型对应关系:

通过表格发现,除了上面定义的 String、Class、Throwable,其他的类(除了数组)都是以 jobject 的形式出现的!事实上 jstring、 jclass 也都是 object 的子类。所以这里还是和 Java 层一样,一切皆 jobject。(当然,如果 jni 在 C 语言中编译的话是没有继承的概念的,此时 jstring、jclass 等其实就是 jobject!用了 typedef 转换而已!!)

接下来是 JNIEnv * 这个指针,它提供了 JNI 中的一系列操作的接口函数。

JNI 中操作 jobject

其实也就是在native层操作 Java 层的实例。 要操作一个实例无疑是:

获取/设置 (即 get/set )成员变量(field)的值;
调用成员方法(method)。
所以问题来了:(挖掘机技术哪家强?! o(*≧▽≦)ツ┏━┓ )

怎么得到 field 和 method?

通过使用 jfieldID 和 jmethodID: 在 JNI 中使用类似于放射的方式来进行 field 和 method 的操作。JNI 中使用j fieldID 和 jmethodID 来表示成员变量和成员方法,获取方式是:

jfieldID GetFieldID(jclass clazz, const char name, const char sig);
jfieldID GetStaticFieldID(jclass clazz, const char name, const char sig);
jmethodID GetMethodID(jclass clazz, const char name, const char sig);
jmethodID GetStaticMethodID(jclass clazz, const char name, const char sig) ;
其中最后一个参数是签名。 获取jclass的方法除了实用上面静态方法的第二个参数外,还可以手动获取。 jclass FindClass(const char *name) 需要注意的是name参数,他是一个类包括包名的全称,但是需要把包名中的点.替换成斜杠/。(好吧,事实上我不是太明白为啥要这么做。)

有了 jfieldID 和 jmethodID 就知道狗蛋住哪了,现在去狗蛋家找他玩 ♪(^∇^*)

  1. get:

<type> Get<type>Field(jobject , jfieldID); 即可获得对应的field,其中field的类型是type,可以是上面类型所叙述的任何一种
<type> GetStatic<type>Field(jobject , jfieldID); 同1,唯一的区别是用来获取静态成员。

  1. set:

void Set<type>Field(jobject obj, jfieldID fieldID, <type> val)
void SetStatic<type>Field(jclass clazz, jfieldID fieldID, <type> value);
成员方法:

调用方法自然要把方法的参数传递进去,JNI中实现了三种参数的传递方式:

Call<type>Method(jobject obj, jmethod jmethodID, ...) 其中 ... 是 C 中的可变长参数,类似于 printf 那样,可以传递不定长个参数。于是你可以把 Java 方法需要的参数在这里面传递进去。
Call<type>MethodV(jobject obj, jmethodID methodID, va_list args) 其中的 va_list 也是 C 中可变长参数相关的内容(我不了解,不敢瞎说,偷懒粘一下Oracle的文档)“Programmers place all arguments to the method in an args argument of type va_list that immediately follows the methodID argument. The CallMethodV routine accepts the arguments, and, in turn, passes them to the Java method that the programmer wishes to invoke.”
Call<type>MethodA(jobject obj, jmethodID methodID, const jvalue args) 哎!这个我知道可以说两句 LOL ~~这里的 jvalue 通过查代码发现就是 JNI 中各个数据类型的 union,所以可以使用任何类型复制!所以参数的传入方式是通过一个 jvalue 的数组,数组内的元素可以是任何 jni 类型。
然后问题又来了:(挖掘机技术到底哪家强?!o(
≧▽≦)ツ┏━┓) 如果传进来的参数和java声明的参数的不一致会怎么样!(即不符合方法签名)这里文档中没用明确解释,但是说道: > Exceptions raised during the execution of the Java method.

typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;

  1. 调用实例方法(instance method):

<type> Call<type>Method(jobject obj, jmethodID methodID, ...); 调用一个具有<type>类型返回值的方法。
<type> Call<type>MethodV(jobject obj, jmethodID methodID, va_list args);
Call<type>MethodA(jobject obj, jmethodID methodID, const jvalue * args)

  1. 调用静态方法(static method):

<type> CallStatic<type>Method(jobject obj, jmethodID methodID, ...);
<type> CallStatic<type>MethodV(jobject obj, jmethodID methodID, va_list args);
CallStatic<type>MethodA(jobject obj, jmethodID methodID, const jvalue * args)

  1. 调用父类方法(super.method),这个就有点不一样了。多了一个 jclass 参数,jclass 可以使 obj 的父类,也可以是 obj 自己的class,但是 methodID 必须是从 jclass 获取到的,这样就可以调用到父类的方法。

<type> CallNonvirtual<type>Method(jobject obj, jclass clazz, jmethodID methodID, ...)
<type> CallNonvirtual<type>MethodV(JNIEnv env, jobject obj, jclass clazz, jmethodID methodID, va_list args);
<type> CallNonvirtual<type>MethodA(JNIEnv
env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args);

转载于:https://blog.51cto.com/13963248/2306463

相关文章:

  • 番外篇——什么叫会工作
  • Python3.6使用tesseract-ocr的正确方法
  • (转)如何上传第三方jar包至Maven私服让maven项目可以使用第三方jar包
  • Java提高篇(一):区分引用变量与对象
  • Elasticsearch 参考指南(升级前重新索引)
  • FreeWheel业务系统微服务化过程经验分享
  • CENTOS7 Python3.7安装 scipy
  • 问:在指定的JSON数据中(最外层是数组)根据指定条件拿到匹配到的结果
  • 函数柯里化
  • 前端页面注意事项
  • javascript 解决跨越问题
  • 基于axios的vue插件,让http请求更简单
  • 解决大并发的问题
  • 关于bootstrap框架美化的实例教程(python)
  • 如何使用Windows Library文件进行持久化
  • 《Java8实战》-第四章读书笔记(引入流Stream)
  • 【面试系列】之二:关于js原型
  • 【刷算法】从上往下打印二叉树
  • Android 控件背景颜色处理
  • github从入门到放弃(1)
  • GitUp, 你不可错过的秀外慧中的git工具
  • gops —— Go 程序诊断分析工具
  • Hibernate最全面试题
  • leetcode讲解--894. All Possible Full Binary Trees
  • Magento 1.x 中文订单打印乱码
  • PHP 使用 Swoole - TaskWorker 实现异步操作 Mysql
  • text-decoration与color属性
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 初识MongoDB分片
  • 得到一个数组中任意X个元素的所有组合 即C(n,m)
  • 多线程事务回滚
  • 关于 Linux 进程的 UID、EUID、GID 和 EGID
  • 坑!为什么View.startAnimation不起作用?
  • 猫头鹰的深夜翻译:JDK9 NotNullOrElse方法
  • 浅谈JavaScript的面向对象和它的封装、继承、多态
  • 深入 Nginx 之配置篇
  • 文本多行溢出显示...之最后一行不到行尾的解决
  • 学习ES6 变量的解构赋值
  • 用element的upload组件实现多图片上传和压缩
  • 昨天1024程序员节,我故意写了个死循环~
  • ​Base64转换成图片,android studio build乱码,找不到okio.ByteString接腾讯人脸识别
  • #13 yum、编译安装与sed命令的使用
  • #QT(智能家居界面-界面切换)
  • #Z0458. 树的中心2
  • (06)金属布线——为半导体注入生命的连接
  • (11)工业界推荐系统-小红书推荐场景及内部实践【粗排三塔模型】
  • (C语言)fgets与fputs函数详解
  • (全注解开发)学习Spring-MVC的第三天
  • (数据结构)顺序表的定义
  • (一)RocketMQ初步认识
  • (已解决)报错:Could not load the Qt platform plugin “xcb“
  • (转) RFS+AutoItLibrary测试web对话框
  • (最优化理论与方法)第二章最优化所需基础知识-第三节:重要凸集举例
  • ***监测系统的构建(chkrootkit )
  • .360、.halo勒索病毒的最新威胁:如何恢复您的数据?