什么都不说,我们先来看代码:
#include <jni.h> /* where everything is defined */
...
JavaVM *jvm; /* denotes a Java VM */
JNIEnv *env; /* pointer to native method interface */
JavaVMInitArgs vm_args; /* JDK/JRE 6 VM initialization arguments */
JavaVMOption* options = new JavaVMOption[1];
options[0].optionString = "-Djava.class.path=/usr/lib/java";
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = 1;
vm_args.options = options;
vm_args.ignoreUnrecognized = false;
/* load and initialize a Java VM, return a JNI interface
* pointer in env */
JNI_CreateJavaVM(&jvm, &env, &vm_args);
delete options;
/* invoke the Main.test method using the JNI */
jclass cls = env->FindClass("Main");
jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V");
env->CallStaticVoidMethod(cls, mid, 100);
/* We are done. */
jvm->DestroyJavaVM();
在不考虑语法正确的情况下,我们可以知道c++调用java的过程:
1.准备工作:定义一些JNI的变量——JavaVM、JNIEnv、JavaVMInitArgs以及JavaVMOption实例;
2. 创建VM: 通过JNI_CreateJavaVM()
函数加载以及初始化一个java虚拟机以及返回一个指向JNI接口的指针。注意:只能在main thread中调用JNI_CreateJavaVM();
3.Attaching to the VM:因为只能在main thread创建VM,所以JNI 接口指针,即JNIEnv只能在main thread中有效。为了在其他的线程也能使用VM:在其他的线程中调用 AttachCurrentThread()
,从而获得JNI 接口指针。注意: (1)一旦attach了VM,该native 线程就像普通的java 线程一样运行在native方法中( Once attached to the VM, a native thread works just like an ordinary Java thread running inside a native method. )。该native 线程保持attach了VM,直到调用DetachCurrentThread()结束;(2)已经attach了VM的线程应该有足够的栈空间来执行相应的工作。每个线程的栈空间大小由操作系统决定。
4. Detaching to the VM:已经attach了VM的native 线程在退出时 必须调用DetachCurrentThread()。注意:如果thread的call stack有java方法,那么该thread不能detach VM。
5. 卸载VM:调用JNI_DestroyJavaVM()卸载VM。注意:在JDK/JRE 1.1,只有主线程才能卸载VM;在JDK/JRE 1.2及其之后,无此限制。
Library以及版本管理:
在JDK/JRE1.1中,一旦一个native library加载成功,那么所有的class loader都是可见的。因此不能的class loader中两个类可能链接相同的native方法,这样会导致两个问题:
1. 一个类可能错误地链接由另外一个class loader中相同类名加载的native库(A class may mistakenly link with native libraries loaded by a class with the same name in a different class loader.)。
2. native 方法能够容易的混合不同class loader的类。这破坏了class loader提供的命名空间分割,由此会导致类型安全问题。
而在JDK/JRE1.2之后,每个class loader管理自己的native library集合。相同的JNI native library只能加载在一个class loader中。如果不这么做就会扔出UnsatisfiedLinkError异常。例如当一个native library加载两个class loader, System.loadLibrary扔出
UnsatisfiedLinkError
,这样做的好处有:
1. 基于class loader的命名空间分隔保留在native library中。一个native library不会容易的混淆不同的class loader;
2. 除此之外,当native libraries相应的class loader在垃圾回收时, 可以安全的卸载native libraries。
为了便于版本控制以及资源管理,JDK/JRE1.2的JNI libraries可以选择使用下面两个函数:
1. jint JNI_OnLoad(JavaVM *vm, void *reserved);
当native library加载时,VM会调用JNI_Onload。JNI_Onload必须返回native library需要的JNI版本。
为了使用任何新的JNI函数,native library必须引出JNI_Onload函数,JNI_Onload函数返回JNI_VERSION_1_2。如果native library没有引出JNI_Onload函数,那么VM假设该native library只需要JNI版本为JNI_VERSION_1_1。如果VM不能识别出由JNI_Onload返回的版本数据,那么native library将不能加载。
2. void JNI_OnUnload(JavaVM *vm, void *reserved);:
当包含了native library的class loader在回收垃圾时,VM将会调用JNI_OnUnload。该函数可以用来做清理操作。
JNI的API函数调用:
JavaVM类型是一个指向API函数表的指针。下面的例子展示了该函数表:
typedef const struct JNIInvokeInterface *JavaVM;
const struct JNIInvokeInterface ... = {
NULL,
NULL,
NULL,
DestroyJavaVM,
AttachCurrentThread,
DetachCurrentThread,
GetEnv,
AttachCurrentThreadAsDaemon
};
注意上面的三个调用的API函数::JNI_GetDefaultJavaVMInitArgs()
, JNI_GetCreatedJavaVMs()
, 以及 JNI_CreateJavaVM(),
他们不是JavaVM函数表的一部分。这三个函数可以在没有预先存在的JavaVM结构的情况下使用。
获得JavaVM的默认设置:
JNI_GetDefaultJavaVMInitArgs jint JNI_GetDefaultJavaVMInitArgs(void *vm_args);
调用该函数之前,native的代码必须设置JNI version的 vm_args->version,并且为期望支持的VM 版本号。如果请求的版本为支持的版本,则返回 JNI_OK
; 否则返回JNI error code (a negative number) 。
获得已经创建的所有JavaVM:
JNI_GetCreatedJavaVMs—— jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);
vmBuf是返回的按照顺序创建并写入到vmBuf中所有VM的指针。注意:在JDK/JRE1.2之后不支持在单进程中创建多个VM。
创建JavaVM:
JNI_CreateJavaVM——jint JNI_CreateJavaVM(JavaVM **p_vm, JNIEnv **p_env, void *vm_args);
还有一些函数基本与上面函数的形式类似:
DestroyJavaVM——jint DestroyJavaVM(JavaVM *vm);
AttachCurrentThread
jint AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args);
AttachCurrentThreadAsDaemon
jint AttachCurrentThreadAsDaemon(JavaVM* vm, void** penv, void* args);DetachCurrentThread
jint DetachCurrentThread(JavaVM *vm);