调用 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 一样的初始化结构。

0x800000000xFFFFFFFF 之间的版本号需保留,并且不为任何虚拟机实现所识别。

以下代码显示了初始化 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 中返回所创建虚拟机的总数。

JDK 1.1 不支持在单个进程中创建多个虚拟机。

参数:

vmBuf:指向将放置虚拟机结构的缓冲区的指针。

bufLen:缓冲区的长度。

nVMs:指向整数的指针。

返回值:

成功时返回“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”;失败则返回负数。

JDK 1.1.2 不支持卸载虚拟机。

 

AttachCurrentThread

jintAttachCurrentThread(JavaVM *vm, JNIEnv **p_env,
void *thr_args);

将当前线程连接到 Java 虚拟机。在 JNIEnv 参数中返回 JNI 接口指针。

试图连接已经连接的线程将不执行任何操作。

本地线程不能同时连接到两个 Java 虚拟机上。

参数:

vm:当前线程所要连接到的虚拟机。

p_env:指向位置(其中放置当前线程的 JNI 接口指针)的指针。

thr_args:特定于虚拟机的线程连接参数。

返回值:

成功时返回“0”;失败则返回负数。

 

DetachCurrentThread

jintDetachCurrentThread(JavaVM *vm);

断开当前线程与 Java 虚拟机之间的连接。释放该线程占用的所有 Java 监视程序。通知所有等待该线程终止的 Java 线程。

主线程(即创建 Java 虚拟机的线程)不能断开与虚拟机之间的连接。作为替代,主线程必须调用 JNI_DestroyJavaVM() 来卸载整个虚拟机。

参数:

vm:当前线程将断开连接的虚拟机。

返回值:

成功时返回“0”;失败则返回负数。


 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐