随着计算机技术的不断发展,多种编程语言之间的相互调用变得越来越常见,如 C++/C++、RPG、COBOL, 以及 Java 语言之间的相互调用。JNI(Java ">Native Interface,也称 java 本机接口)是一系列标准的 Java API。它支持 Java 语言和其它编程语言之间的相互调用。但是,JNI 是一把双刃剑,为开发人员带来极大的便利的同时也引入了潜在的威胁。如果使用不当,就会导致应用程序性能降低甚至整个应用程序都会崩溃。因此,如何正确有效的使用 JNI 技术变得越来越重要。
IBM i 将 Java 环境和 ILE(Integrated language environment)环境分离开来,java 不属于一种 ILE 语言,它不能被 bind 到 ILE 对象模型里去创建 program 或者 service program。要实现 java 和其他 ILE 语言之间的相互调用,就需要使用 JNI 提供的接口函数。
JNI 主要包含两大部分:Native methods 和 Java Invocation API。本文将重点介绍后者。
Native methods 是指通过非 java 语言实现的 java method,native method 可以访问一些系统级别的函数和 API,而这些函数是不能在 java 中直接调用的。但是,调用这些 API 会降低 Java 应用程序的可移植性。
Java Invocation API 提供了一系列接口函数,允许非 Java 代码创建和初始化 JVM\、加载 Class、调用 Java method 和销毁 JVM 等。Java 提供的这些接口函数可以使非 Java 多线程程序充分利用同一个 JVM 中的 java classes。
IBM i 上 Java Invocation API 主要提供给两类调用者:
ILE program 或者 service program, 这些程序需要支持 teraspace storage 模式。 IBM i PASE 可执行程序。
Java Invocation API
非 Java 程序要调用 Java 程序,必须首先创建 JVM,然后才能由 JVM 解释执行 class 文件。JNI 提供了一系列的接口函数,通过这些函数可以方便地创建和初始化 JVM。下面简单介绍常见的一些 Java 调用接口函数。
获得当前 job 已经创建的所
有的 JVM 函数: jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf,jsize bufLen,jsize *nVMs); 参数说明:vmBuf 是一个输出参数,存储指向 JavaVM 结构的指针,长度由参数 bugLen 决定。每一个 JVM
都有一个对应的 JavaVM 结构。nVMs 返回已经创建的 JVM 的数量,但 IBM i 上同一个 Job 只允许存在一个 JVM,因此 nVMs 的最大值是 1. 创建和初始化 JVM 函数: jint JNI_CreateJavaVM(JavaVM **p_vm,void **p_env,void *vm_args); 参数说明:p_vm 新创建的 JVM 指针,指向结构体 JavaVM, 其它的 JNI invocation API 会使用这个变量去识别 JVM。 p_env 新创建的 JVM 的 JNI 环境变量指针,它指向一个 JNI 函数表,贯穿于整个调用过程中,JNI 函数表中的函数调用都需要这个参数。vm_args 是一个结构体,包含 JVM 初始化的一些参数。当然,JVM 的参数有多种不同设置方式,本文不做具体介绍,更多相关信息,请参考(DW link) 注意: IBM i 上,同一个 job 或 process 里,Java 只允许同时存在一个 JVM。 销毁 JVM 函数: jint DestroyJavaVM(JavaVM *vm) 参数说明:vm 是指向要销毁的 JVM。 注意:在确认这个 JVM 已经不再被任何线程使用的情况下再去销毁 JVM。当 Job 结束的时候,JVM 会自动销毁,同时释放占用的资源并回收内存。 Attach 已经存在的 JVM 函数: jint AttachCurrentThread(JavaVM *vm,void **p_env,void *thr_args); 参数说明:vm 是表示某个 JVM 的 JavaVM 指针,它指向当前 thread 要 attach 到哪一个 JVM。p_env 当前 thread 的 JNI 环境指针。thr_args 包含 thread 特有的参数信息。 Detach 已经 attach 的 JVM 函数: jint DetachCurrentThread(JavaVM *vm); 参数说明: vm 是指向要 detach 的 JVM。
非 Java 程序如何创建和使用 JVM
在了解 Java 调用接口的基础上,要正确使用这些接口函数,还需要弄清楚非 Java 程序调用 Java 程序的基本步骤。下面是一个简单的步骤:
通过 JNI_CreateJavaVM() 创建 JVM 或者通过 AttachCurrentThread() attach 到已经存在的 JVM 上。 通过 JVM 找到想要运行的类 (class)。 找到类 (class) 中要调用函数的 methodID。 通过 methodID 调用这个函数。
在非 Java 程序中创建 JVM 有两种方法,一种是通过 JNI 调用来创建 JVM,另一种是由编程语言运行时环境帮助创建 JVM。当用户调用 java method 的时候,运行时环境会自动检测当前 job 有没有已经创建的 JVM,如果没有则调用 JNI_CreateJavaVM() 来创建 JVM,如果有则直接 attach 上去。RPG 语言运行时环境就提供了这种支持(详见"RPG call java"小节)。
IBM i 上,同一个 Job 只允许同时存在一个 JVM,因此 JNI_CreateJavaVM() 在同一个 job 中只能被成功的调用一次。如果当用户的应用程序需要调用 Java 方法的时候,当前 Job 没有已经创建的 JVM,那么需要显示(用户自己调用)或隐式(通过不同语言的 Runtime )的调用 JNI_CreateJavaVM() 来启动 JVM,这时当前 job 已经存在一个 JVM,如果其它的线程还需要调用 Java 方法,就不能再重新创建 JVM,只能调用 AttachCurrentThread() 去 attach 到当前 job 中的 JVM 上即可继续调用 Java 程序。
注意: 当用户创建 *PGM 或 *SRVPGM 的时候,QJVAJNI *SRVPGM 提供了 API 函数 JNI_CreateJavaVM() 的接口,该函数负责创建 JVM。 但是,QJVAJNI 没有初始化 API 函数 AttachCurrentThread() ,因此,像调用 JNI_CreateJavaVM() 一样调用 AttachCurrentThread() 会出错,这是通过 JNI 调用方式创建 JVM 时经常出现的一个问题。AttachCurrentThread() 函数需要通过 JavaVM 结构指针来调用,该结构存放在 QSYSINC 的 JNI 头文件里。下一小节会给出正确的调用示例。