JNI
Sun JNI
-
JNI overview
-
JNI 知识脉络
-
JNI design
-
JNI 暴露给 native 的是一个 Pointer.Java 调用 native 的方法时,这个 Pointer 作为一个参数(即 jnienv,提供了很多操作 JVM 的方法,这些方法叫作 JNI 方法)
-
编译/ 链接 /加载
-
native 方法参数
-
native 引用 JVM 对象
-
基本类型数据是直接 copy value 的
-
其余类型传递引用到 native。所以
-
-
native 代码调用 JNI 方法时,JNI 方法可能会抛出错误。native 代码应该在调用 JNI 方法后,调用
ExceptionOccurred()
来检查是不是发生了错误,获取 pendingException,并且做相应的处理。如果没有处理 pendingException 就调用其他的 JNI 方法,即有可能会发生错误,只有少数几个 JNI 方法在有 pendingException 的情况下可以正常运行 -
native 代码调用 JNI 方法
ExceptionClear()
来表示 pendingException 已经被处理了。 -
native 代码 return 的时候,有未处理的 pendingException,会在 java 代码里 raise this pendingException
-
native 代码可以调用 JNI 方法
ThrowNew(jnienv*, exceptionClassObject, exceptionMsgString)
来主动 raise an exception. -
多线程时的异常处理。?一个线程需要在适当的时候调用
ExceptionOccurred()
来获取是否另一个线程有异常发生,这里我不能很好的理解。
-
-
-
- 基础类型
- boolean -> jboolean;
- byte -> jbyte
- ...
- 引用类型
jobject / jclass / jstring / jarray / jXXXarray / jthrowable
- C++里面这些类型有父子继承关系,C 里面都是 jobject.
- type signature
- 基础类型:
Zboolean Bbyte Cchar Sshort Iint Jlong Ffloat Ddouble
- 类:
L fully-qualified-class ;
,比如Ljava/lang/String;
- 数组:
[type
,比如[B
- 方法:
( arg-types ) ret-type
,比如(ILjava/lang/String;[I)J
- 基础类型:
- Modified UTF-8 String
- 基础类型
-
jnienv*
是一个指针,指向一个包含所有 JNI 方法指针的struct
。- functions 分类具体的 jni functions.
- Version @R_525_4045@ion
- Class Operations
- Exceptions
- Global and Local References
- Weak Global References
- Object Operations
- Accessing Fields of Objects
- Calling Instance Methods
- Accessing Static Fields
- Calling Static Methods
- String Operations
- Array Operations
- Registering Native Methods
- Monitor Operations
- NIO Support
- Reflection Support
- Java VM Interface
-
Invocation API 。 用来给独立的 native 代码(即不是从 java 的
System.loadLibrary
加载的 native 代码)操作 JVM 的 API-
可以主动新建一个
JVM
;让 JVM 加载一个指定的class
;执行类的某些方法或者进行某些操作(就像一般的 native 代码执行 jvm 方法一样)。 -
JVM 加载 native library
- jdk1.2 后,native library 跟自己类所在的 classLoader 绑定。一旦所在类的 classLoader 被卸载了,native library 也会被清除;一个 JVM 只能加载一个 native library 一次
- native library 可以提供一个方法
jint JNI_OnLoad(JavaVM *vm, void *reserved);
在System.loadLibrary
时 JVM 主动调用,以获取 native library 要求的 JVM 版本号(比如JNI_VERSION_1_2
这些都是已定义好的int
常量)。 - native library 可以提供一个方法
void JNI_OnUnload(JavaVM *vm, void *reserved);
在包含 native library 的 class loader 被 gc 的时候由 JVM 主动调用,以让 native code 执行一些必要的内存清理工作(比如释放 global reference 等)。
-
Invocation API functions
native 可以用来主动操作 JVM 的方法(全局方法,不需要调用env->XXX
,需要在 native code 里#include <jni.h>
)。jint JNI_GetDefaultJavaVMInitArgs(void *vm_args);
native code 调用这个方法来获取 JVM 的默认配置参数,vm_args
是一个指向JavaVMInitArgs
结构的指针。
// JavaVMInitArgs结构 typedef struct JavaVMInitArgs { jint version; jint noptions; JavaVMOption *options; jboolean ignoreUnrecognized; } JavaVMInitArgs; // JavaVMOption typedef struct JavaVMOption { char *optionString; /* the option as a string in the default platform encoding */ void *extraInfo; } JavaVMOption; // JavaVM结构 typedef const struct JNIInvokeInterface *JavaVM; const struct JNIInvokeInterface ... = { NULL, NULL, NULL, DestroyJavaVM, AttachCurrentThread, DetachCurrentThread, GetEnv, AttachCurrentThreadAsDaemon };
-
从 JDK1.2 开始,不支持一个进程里有多个 JVM 了。所以下面的几个方法都受到影响(比如只能获得一个 JVM,或者是)
-
jint DestroyJavaVM(JavaVM *vm);
将当前线程 attatch 到 JVM 上,在该线程成为 JVM 的唯一用户线程时,退出该 JVM 并且释放其占有的资源。jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args);
把当前线程加到 JVM 里,参数p_env
接收加进去以后的JNI Interface
的指针,thr_args
是增加线程到 JVM 的参数,结构如下:
typedef struct JavaVMAttachArgs { jint version; /* must be at least JNI_VERSION_1_2 */ char *name; /* the name of the thread as a modified UTF-8 string, or NULL */ jobject group; /* global ref of a ThreadGroup object, or NULL */ } JavaVMAttachArgs
jint AttachCurrentThreadAsDaemon(JavaVM* vm, void** p_env, void* args);
把当前线程作为 Daemon thread加到 JVM 里。如果已经加过了,单纯地设置p_env
的值,不会改变已经添加的线程的 daemon 状态(即如果之前不是 daemon,调用这个方法并不会让其变成 daemon)jint DetachCurrentThread(JavaVM *vm);
取消 JVM 里的当前线程,其占有的锁都会释放掉。jint GetEnv(JavaVM *vm, void **env, jint version);
获取 JVM 中当前线程对应的JNI Interface
,version
参数为要求的 JVM version,如果实际的 JVM 不支持指定的 version 的话(比如实际为1.1
要求的却是1.2
),会返回错误,
-
-
Java 命令行中使用 jni
- 编写 java/kt 代码,注册 native 方法,在
static
代码块中执行System.loadLibrary
(对于 kt 为 companion object 的init
block). - 通过 java/kt 代码生成 class 文件
- 使用
javac
或者kotlinc
(在 AS 的 plugins 中有该工具),生成.class
.
- 使用
- 使用
javah X.class
对生成的.class
文件,生成所需的 C header file ,.h
- 编写
.h
对应的.c
文件,在其中实现方法声明的方法。 - 调用
gcc -c X.c
来生成.o
文件 - 调用
gcc -shared -o X.so X.o
来生成.so
文件,得到共享库。(在 linux 上为libXXX.so
,在 Mac 上为libXXX.jnilib
) - 调用
java
执行有 jni 参与的 java 类。- 使用
-Djava.library.path=""
来引用所有的 jni 库 - 使用
-cp
来指定所需的 class 或者 jar. - java_command.md
- 使用
问题
何时加载 so
在调用 native
方法前的任何时间都可以.通常在类的 static
代码块中进行加载.
jni 方法是如何进行注册的.
- 静态注册 : 通过
javah
生成.h
文件,实现其中的方法. 优点: 简单; 缺点 : 方法名长. - 动态注册 : 通过在
jNI_OnLoad
方法中调用jnienv.registerNatives
来进行注册,其中参数有java 方法名
和c 方法指针
的对应.
jni 的 java 层和 c 层的参数类型如何转换? Integer 会转成什么类型? string 呢?
- 除了 string/class/Throwable 外的 Object 都转成
jobject
. - Integer 是 jobject , string 是 jstring
jnienv 是线程相关的吗?
- 是的, jnienv 是线程独立的.
JNI 如何在 native 调用 java 的方法? 如何获取一个对象的属性?
- 找到 jclass -> 通过
jnienv.getmethodId(jclass, methodName, methodSig)
获取 jMethodID -> 通过jnienv.callVoidMethod(obj, methodId, params)
;
对于static
方法要使用jnienv.callStaticVoidMethod
(可能是因为涉及到方法的分派) - 对象的成员变量需要用
getByyteField
等方法来获取.
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。