调用 API
调用 API 允许软件厂商将 Java 虚拟机加载到任意的本地程序中。厂商可以交付支持 Java 的应用程序,而不必链接 Java 虚拟机源代码。
本章首先概述了调用 API。然后是所有调用 API 函数的引用页。
若要增强 Java 虚拟机的嵌入性,可以用几种方式来扩展 JDK 1.1.2 中的调用 API。
概述
以下代码示例说明了如何使用调用 API 中的函数。在本例中,C++ 代码创建 Java 虚拟机并且调用名为 Main.test
的静态方法。为清楚起见,我们略去了错误检查。
#include <jni.h> /* 其中定义了所有的事项 */
...
JavaVM *jvm; /* 表示 Java 虚拟机*/
JNIEnv *env; /* 指向本地方法接口的指针 */
JDK1_1InitArgs vm_args; /* JDK 1.1 虚拟机初始化参数 */
vm_args.version = 0x00010001; /* 1.1.2 中新增的:虚拟机版本 */
/* 获得缺省的初始化参数并且设置类
* 路径 */
JNI_GetDefaultJavaVMInitArgs(&vm_args);
vm_args.classpath = ...;
/* 加载并初始化 Java 虚拟机,返回 env 中的
* JNI 接口指针 */
JNI_CreateJavaVM(&jvm, &env, &vm_args);
/* 用 JNI 调用 Main.test 方法 */
jclass cls = env->FindClass("Main");
jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V");
env->CallStaticVoidMethod(cls, mid, 100);
/* 结束。*/
jvm->DestroyJavaVM();
本例使用了 API 中的三个函数。调用 API 允许本地应用程序用 JNI 接口指针来访问虚拟机特性。其设计类似于 Netscape 的 JRI 嵌入式接口。
创建虚拟机
JNI_CreateJavaVM() 函数加载并初始化Java 虚拟机,然后将指针返回到 JNI 接口指针。调用 JNI_CreateJavaVM()
的线程被看作主线程。
连接虚拟机
JNI 接口指针 (JNIEnv
) 仅在当前线程中有效。如果另一个线程需要访问 Java 虚拟机,则该线程首先必须调用AttachCurrentThread()
以将自身连接到虚拟机并且获得 JNI 接口指针。连接到虚拟机之后,本地线程的工作方式就与在本地方法内运行的普通 Java 线程一样了。本地线程保持与虚拟机的连接,直到调用 DetachCurrentThread()
时才断开连接。
卸载虚拟机
主线程不能自己断开与虚拟机的连接。而是必须调用DestroyJavaVM()
来卸载整个虚拟机。
虚拟机等到主线程成为唯一的用户线程时才真正地卸载。用户线程包括 Java 线程和附加的本地线程。之所以存在这种限制是因为 Java 线程或附加的本地线程可能正占用着系统资源,例如锁,窗口等。虚拟机不能自动释放这些资源。卸载虚拟机时,通过将主线程限制为唯一的运行线程,使释放任意线程所占用系统资源的负担落到程序员身上。
初始化结构
不同的 Java 虚拟机实现可能会需要不同的初始化参数。很难提出适合于所有现有和将来的 Java 虚拟机的标准初始化结构。作为一种折衷方式,我们保留了第一个域 (version
) 来识别初始化结构的内容。嵌入到 JDK 1.1.2 中的本地应用程序必须将版本域设置为 0x00010001
。尽管其它实现可能会忽略某些由 JDK 所支持的初始化参数,我们仍然鼓励虚拟机实现使用与 JDK 一样的初始化结构。
0x80000000
到 0xFFFFFFFF
之间的版本号需保留,并且不为任何虚拟机实现所识别。
以下代码显示了初始化 JDK 1.1.2 中的 Java 虚拟机所用的结构。
typedef struct JavaVMInitArgs {
/* 前两个域在 JDK 1.1 中保留,并
在 JDK 1.1.2 中正式引入。*/
/* Java 虚拟机版本 */
jint version;
/* 系统属性。*/
char **properties;
/* 是否检查 Java 源文件与已编译的类文件
*之间的新旧关系。*/
jint checkSource;
/* Java 创建的线程的最大本地堆栈大小。*/
jint nativeStackSize;
/* 最大 Java 堆栈大小。*/
jint javaStackSize;
/* 初始堆大小。*/
jint minHeapSize;
/* 最大堆大小。*/
jint maxHeapSize;
/* 控制是否校验 Java 字节码:
* 0 无,1 远程加载的代码,2 所有代码。*/
jint verifyMode;
/* 类加载的本地目录路径。*/
const char *classpath;
/* 重定向所有虚拟机消息的函数的钩子。*/
jint (*vfprintf)(FILE *fp, const char *format,
va_list args);
/* 虚拟机退出钩子。*/
void (*exit)(jint code);
/* 虚拟机放弃钩子。*/
void (*abort)();
/* 是否启用类 GC。*/
jint enableClassGC;
/* GC 消息是否出现。*/
jint enableVerboseGC;
/* 是否允许异步 GC。*/
jint disableAsyncGC;
/* 三个保留的域。*/
jint reserved0;
jint reserved1;
jint reserved2;
} JDK1_1InitArgs;
在 JDK 1.1.2 中,初始化结构提供了钩子,这样在虚拟机终止时,本地应用程序可以重定向虚拟机消息并获得控制权。
当本地线程与JDK 1.1.2 中的 Java 虚拟机连接时,以下结构将作为参数进行传递。实际上,本地线程与 JDK 1.1.2 连接时不需要任何参数。JDK1_1AttachArgs
结构仅由 C 编译器的填充槽组成,而 C 编译器不允许空结构。
typedef struct JDK1_1AttachArgs {
/*
* JDK 1.1 不需要任何参数来附加
* 本地线程。此处填充的作用是为了满足不允许空结构的 C
* 编译器的要求。
*/
void *__padding;
} JDK1_1AttachArgs;
调用 API 函数
JavaVM 类型是指向调用 API 函数表的指针。以下代码示例显示了这种函数表。
typedef const struct JNIInvokeInterface *JavaVM;
const struct JNIInvokeInterface ... = {
NULL,
NULL,
NULL,
DestroyJavaVM,
AttachCurrentThread,
DetachCurrentThread,
};
注意,JNI_GetDefaultJavaVMInitArgs()
、JNI_GetCreatedJavaVMs() 和JNI_CreateJavaVM()
这三个调用 API 函数不是 JavaVM 函数表的一部分。不必先有 JavaVM
结构,就可以使用这些函数。
JNI_GetDefaultJavaVMInitArgs
jintJNI_GetDefaultJavaVMInitArgs(void *vm_args);
返回 Java 虚拟机的缺省配置。在调用该函数之前,平台相关代码必须将 vm_args->version
域设置为它所期望虚拟机支持的 JNI 版本。在 JDK 1.1.2 中,必须将 vm_args->version
设置为 0x00010001
。(JDK1.1 不要求平台相关代码设置版本域。为了向后兼容性,如果没有设置版本域,则 JDK 1.1.2 假定所请求的版本为 0x00010001。JDK 的未来版本将要求把版本域设置为适当的值。) 该函数返回后,将把vm_args->version
设置为虚拟机支持的实际 JNI 版本。
参数:
vm_args:指向 VM-specific initialization
(特定于虚拟机的初始化)结构的指针,缺省参数填入该结构。
返回值:
如果所请求的版本得到支持,则返回“0”;如果所请求的版本未得到支持,则返回负数。
JNI_GetCreatedJavaVMs
jintJNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen,
jsize *nVMs);
返回所有已创建的Java 虚拟机。将指向虚拟机的指针依据其创建顺序写入 vmBuf 缓冲区。最多写入 bufLen 项。在 *nVMs 中返回所创建虚拟机的总数。
参数:
vmBuf:指向将放置虚拟机结构的缓冲区的指针。
返回值:
成功时返回“0”;失败则返回负数。
JNI_CreateJavaVM
jintJNI_CreateJavaVM(JavaVM **p_vm, JNIEnv **p_env,
void *vm_args);
加载并初始化Java 虚拟机。当前线程成为主线程。将env
参数设置为主线程的 JNI 接口指针。
JDK 1.1.2 不支持在单个进程中创建多个虚拟机。必须将 vm_args 中的版本域设置为 0x00010001
。
参数:
p_vm:指向位置(其中放置所得到的虚拟机结构)的指针。
p_env
:指向位置(其中放置主线程的 JNI 接口指针)的指针。
vm_args
: Java 虚拟机初始化参数。
返回值:
成功时返回“0”;失败则返回负数。
DestroyJavaVM
jintDestroyJavaVM(JavaVM *vm);
卸载 Java 虚拟机并回收资源。只有主线程能够卸载虚拟机。调用 DestroyJavaVM()
时,主线程必须是唯一的剩余用户线程。
参数:
vm:将销毁的 Java 虚拟机。
返回值:
成功时返回“0”;失败则返回负数。
AttachCurrentThread
jintAttachCurrentThread(JavaVM *vm, JNIEnv **p_env,
void *thr_args);
将当前线程连接到 Java 虚拟机。在 JNIEnv
参数中返回 JNI 接口指针。
参数:
vm:当前线程所要连接到的虚拟机。
p_env
:指向位置(其中放置当前线程的 JNI 接口指针)的指针。
thr_args
:特定于虚拟机的线程连接参数。
返回值:
成功时返回“0”;失败则返回负数。
DetachCurrentThread
jintDetachCurrentThread(JavaVM *vm);
断开当前线程与 Java 虚拟机之间的连接。释放该线程占用的所有 Java 监视程序。通知所有等待该线程终止的 Java 线程。
主线程(即创建 Java 虚拟机的线程)不能断开与虚拟机之间的连接。作为替代,主线程必须调用 JNI_DestroyJavaVM()
来卸载整个虚拟机。
参数:
vm:当前线程将断开连接的虚拟机。
返回值:
成功时返回“0”;失败则返回负数。