译文地址:
http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html#wp9502
<h1>第五章<h1>
Invocation API允许软件供应商将Java VM加载到任意本机应用程序中。供应商可以提供支持Java的应用程序,而不必与Java VM源代码链接。
本章从Invocation API的概述开始。接下来是所有Invocation API函数的参考页面为了增强Java VM的可嵌入性,Invoke API在JDK 1.1.2中以少量方式扩展。
概观
以下代码示例说明了如何在Invocation API中使用函数。在这个例子中,C ++代码创建一个Java VM并调用一个称为Main.test的静态方法。为了清楚起见,我们省略错误检查。
#include <jni.h> / *其中一切都定义* /
...
JavaVM * jvm; / *表示Java VM * /
JNIEnv * env; / *指向本机方法的指针* /
JDK1_1InitArgs vm_args; / * JDK 1.1 VM初始化参数* /
vm_args.version = 0x00010001; / * 1.1.2中的新功能:VM版本* /
/ *获取默认的初始化参数并设置类
*路径* /
JNI_GetDefaultJavaVMInitArgs(&vm_args);
vm_args.classpath = ...;
/ *加载和初始化Java VM,返回一个JNI接口
*指针在env * /
JNI_CreateJavaVM(&jvm,&env,&vm_args);
/ *调用Main.test方法使用JNI * /
jclass cls = env-> FindClass(“Main”);
jmethodID mid = env-> GetStaticMethodID(cls,“test”,“(I)V”);
env-> CallStaticVoidMethod(cls,mid,100);
/* 我们完了。 * /
jvm-> DestroyJavaVM();
此示例在API中使用三个函数。 Invocation API允许本机应用程序使用JNI接口指针来访问VM功能。该设计类似于Netscape的JRI嵌入式界面。
创建虚拟机
JNI_CreateJavaVM()函数加载并初始化Java VM,并返回指向JNI接口指针的指针。调用JNI_CreateJavaVM()的线程被认为是主线程。
连接到虚拟机
JNI接口指针(JNIEnv)仅在当前线程中有效。如果另一个线程需要访问Java VM,则必须首先调用AttachCurrentThread()将其自身附加到VM并获取JNI接口指针。一旦连接到VM,本机线程就像在本机方法中运行的普通Java线程一样工作。本机线程仍然连接到VM,直到它调用DetachCurrentThread()来自行分离。
附加的线程应该有足够的堆栈空间来执行可重复的工作量。每个线程的堆栈空间分配是操作系统特定的。例如,使用pthreads,可以在pthread_attr_t参数中指定堆栈大小为pthread_create。
卸载虚拟机
主线程无法从虚拟机分离。相反,它必须调用DestroyJavaVM()来卸载整个VM。
VM等待直到主线程在实际卸载之前是唯一的用户线程。用户线程包括Java线程和附加的本机线程。存在此限制,因为Java线程或附加的本机线程可能正在保存系统资源,例如锁,窗口等。虚拟机无法自动释放这些资源。通过将主线程限制为VM卸载时唯一运行的线程,释放由任意线程保存的系统资源的负担在程序员身上。
<h1>库和版本管理<h1>
在JDK 1.1中,一旦加载本地库,它就可以从所有的类加载器中看到。因此,不同类加载器中的两个类可以与相同的本机方法链接。这会导致两个问题:
类可能会错误地链接到由不同类加载器中具有相同名称的类加载的本机库。
本机方法可以轻松地从不同的类加载器混合类。这打破了类装载机提供的名称空间分离,并导致类型安全问题。
在JDK中,每个类加载器都管理自己的一组本机库。相同的JNI本地库不能加载到多个类加载器中。这样做会引起“不满意的链接错误”。例如,System.loadLibrary在将本机库加载到两个加载器中时抛出一个UnsatisfiedLinkError。新方法的好处是:基于类加载器的名称空间分隔保留在本机库中。一个本机库不能轻易地混合来自不同类加载器的类。
另外,本地库可以在相应的类加载器被垃圾回收时卸载。
为了方便版本控制和资源管理,Java 2 Platform中的JNI库可以选择导出以下两个功能:
JNI_OnLoad
--
jint JNI_OnLoad(JavaVM * vm,void * reserved);
加载本地库时,VM会调用JNI_OnLoad(例如,通过System.loadLibrary)。 JNI_OnLoad必须返回本机库所需的JNI版本。
为了使用任何新的JNI函数,本机库必须导出返回JNI_VERSION_1_2的JNI_OnLoad函数。如果本机库不导出JNI_OnLoad函数,则虚拟机假定库仅需要JNI版本JNI_VERSION_1_1。如果VM无法识别JNI_OnLoad返回的版本号,则无法加载本机库。
联系:
从包含本机方法实现的本机库导出。
从JDK / JRE 1.4:
为了使用J2SE版本1.2中引入的JNI函数,除了JDK 1.1中可用的JNI函数之外,本机库必须导出返回JNI_VERSION_1_2的JNI_OnLoad函数。
为了使用J2SE 1.4版中引入的JNI函数,除了版本1.2中可用的JNI函数之外,本机库必须导出返回JNI_VERSION_1_4的JNI_OnLoad函数。
如果本机库不导出JNI_OnLoad函数,则虚拟机假定库仅需要JNI版本JNI_VERSION_1_1。如果VM无法识别JNI_OnLoad返回的版本号,则无法加载本机库。
JNI_OnUnload
void JNI_OnUnload(JavaVM * vm,void * reserved);
当包含本地库的类加载器被垃圾回收时,VM调用JNI_OnUnload。此功能可用于执行清理操作。因为这个函数在一个未知的上下文(例如从一个finalizer)中被调用,所以程序员在使用Java VM服务时应该是保守的,并且避免任意的Java回调。
请注意,JNI_OnLoad和JNI_OnUnload是JNI库可选提供的两个函数,不是从VM导出的。
联系:
从包含本机方法实现的本机库导出。
调用API函数
JavaVM类型是一个指向Invocation API函数表的指针。以下代码示例显示此功能表。
typedef const struct JNIInvokeInterface * JavaVM;
const struct JNIInvokeInterface ... = {
空值,
空值,
空值,
DestroyJavaVM,
AttachCurrentThread,
DetachCurrentThread,
GETENV,
AttachCurrentThreadAsDaemon
};
请注意,三个Invocation API函数JNI_GetDefaultJavaVMInitArgs(),
JNI_GetCreatedJavaVMs()和JNI_CreateJavaVM()不是JavaVM功能表的一部分。这些功能可以在没有预先存在的JavaVM结构的情况下使用。
JNI_GetDefaultJavaVMInitArgs
jint JNI_GetDefaultJavaVMInitArgs(void * vm_args);返回Java VM的默认配置。在调用此函数之前,本机代码must1将vm_args-> version字段设置为它希望VM支持的JNI版本。在JDK 1.1.2中,vm_args->版本必须设置为0x00010001。此函数返回后,vm_args->版本将被设置为VM支持的实际JNI版本。
联系:
从实现Java虚拟机的本机库导出。
参数:
vm_args:指向VM特定的初始化结构的指针,默认参数被填充到该初始化结构中。
返回值:
如果支持请求的版本,返回“0”如果不支持请求的版本,则返回一个负数。
JNI_GetCreatedJavaVMs
jint JNI_GetCreatedJavaVMs(JavaVM ** vmBuf,jsize bufLen,jsize * nVMs);返回已创建的所有Java VM。指向VM的指针按照创建的顺序写入缓冲区vmBuf。最多写入数量的条目将被写入。创建的VM的总数在* nVMs中返回。
JDK 1.1.2不支持在单个进程中创建多个VM。
联系:
从实现Java虚拟机的本机库导出。
参数:
vmBuf:指向将要放置VM结构的缓冲区的指针。
bufLen:缓冲区的长度。
nVMs:指向整数的指针。
返回值:
成功返回“0”在失败时返回负数。
JNI_CreateJavaVM
jint JNI_CreateJavaVM(JavaVM ** p_vm,JNIEnv ** p_env,void * vm_args);加载并初始化Java VM。当前线程成为主线程。将env参数设置为主线程的JNI接口指针。
JDK 1.1不支持在单个进程中创建多个虚拟机。 vm_args中的version字段必须设置为0x00010001。
在JDK 1.1中,JNI_CreateJavaVM的第二个参数始终是指向JNIEnv *的指针。第三个参数是指向JDK 1.1特定结构(JDK1_1InitArgs)的指针。JDK1_1InitArgs结构显然不是设计为在所有VM上可移植。
在JDK中,我们引入了一个标准的VM初始化结构。向后兼容性保留。如果VM初始化参数指向JDK1_1InitArgs结构,则JNI_CreateJavaVM仍返回1.1版本的JNI接口指针。如果第三个参数指向JavaVMInitArgs结构,VM将返回1.2版本的JNI接口指针。与包含一组固定选项的JDK1_1InitArgs不同,JavaVMInitArgs使用选项字符串来编码任意VM启动选项。
typedef struct JavaVMInitArgs {
jint version;
jint nOptions;
JavaVMOption *options;
jboolean ignoreUnrecognized;
} JavaVMInitArgs;
版本字段必须设置为JNI_VERSION_1_2。 (相反,JDK1_1InitArgs中的版本字段必须设置为JNI_VERSION_1_1。)options字段是以下类型的数组:
typedef struct JavaVMOption {
char * optionString; / *该选项作为字符串在默认平台编码* /
void * extraInfo;
} JavaVMOption;
数组的大小由JavaVMInitArgs中的nOptions字段表示。如果ignoreUnrecognized是JNI_TRUE,JNI_CreateJavaVM将忽略以“-X”或“_”开头的所有无法识别的选项字符串。如果ignoreUnrecognized是JNI_FALSE,JNI_CreateJavaVM在遇到任何无法识别的选项字符串时立即返回JNI_ERR。所有Java VM必须识别以下标准选项集:
-D <name> = <value>设置系统属性
-verbose [:class | gc | jni]启用详细输出。这些选项之后可以用逗号分隔的名称列表来指示VM将打印哪种类型的消息。例如,“-verbose:gc,class”指示VM打印GC和类加载相关的消息。标准名称包括:gc,class和jni。所有非标准(VM特定)名称必须以“X”开头。
vfprintf extraInfo是指向vfprintf钩子的指针。
exit extraInfo是一个指向退出钩子的指针。
abort extraInfo是指向中止挂钩的指针。
此外,每个VM实现可以支持其自己的一组非标准选项字符串。非标准选项名称必须以“-X”或下划线(“_”)开头。例如,JDK支持-Xms和-Xmx选项,以允许程序员指定初始和最大堆大小。从“-X”开始的选项可以从“java”命令行访问。
以下是在JDK中创建Java VM的示例代码:
JavaVMInitArgs vm_args;
JavaVMOption选项[4];
options [0] .optionString =“-Djava.compiler = NONE”; / *禁用JIT * /
options [1] .optionString =“-Djava.class.path = c:\ myclasses”; / *用户类* /
options [2] .optionString =“-Djava.library.path = c:\ mylibs”; / *设置本机库路径* /
options [3] .optionString =“-verbose:jni”; / *打印与JNI相关的消息* /
vm_args.version = JNI_VERSION_1_2;
vm_args.options = options;
vm_args.nOptions = 4;
vm_args.ignoreUnrecognized = TRUE;
/ *请注意,在JDK中,不再需要调用
* JNI_GetDefaultJavaVMInitArgs。
* /
res = JNI_CreateJavaVM(&vm,(void **)&env,&vm_args);
if(res <0)...
JDK仍然支持与JDK 1.1完全相同的JDK1_1InitArgs。
联系:
从实现Java虚拟机的本机库导出。
参数:
p_vm:指向生成的VM结构的位置的指针。
p_env:指向主线程的JNI接口指针的位置的指针。
vm_args:Java VM初始化参数。
返回值:
成功返回“0”在失败时返回负数。
DestroyJavaVM
jint DestroyJavaVM(JavaVM * vm);卸载Java VM并收回资源。只有主线程可以卸载虚拟机。系统等待,直到主线程在其销毁虚拟机之前只剩下用户线程。
对DestroyJavaVM的支持在1.1中尚未完成。只有主线程可以调用DestroyJavaVM。在JDK中,任何线程(无论是否附加)都可以调用此函数。如果连接当前线程,则VM等待直到当前线程是唯一的用户级Java线程。如果当前线程未附加,则VM附加当前线程,然后等待直到当前线程为唯一的用户级线程。然而,JDK仍然不支持VM卸载。 DestroyJavaVM总是返回错误代码。
联系:
索引3在JavaVM界面的功能表中。
参数:
vm:将被销毁的Java VM。
返回值:
成功返回“0”在失败时返回负数。
JDK 1.1.2不支持卸载VM。
AttachCurrentThread
jint AttachCurrentThread(JavaVM * vm,JNIEnv ** p_env,void * thr_args);将当前线程附加到Java VM。返回JNIEnv参数中的JNI接口指针。
尝试附加已经附加的线程是无操作的。
本机线程不能同时连接到两个Java VM。
当线程附加到VM时,上下文类加载器是引导加载程序。
联系:
索引4在JavaVM界面功能表中。
参数:
vm:当前线程将附加到的VM。
p_env:指向当前线程的JNI接口指针的位置的指针。
thr_args:VM特定的线程附件参数。
在JDK 1.1中,AttachCurrentThread的第二个参数总是指向JNIEnv的指针。
AttachCurrentThread的第三个参数被保留,并应设置为NULL。
在JDK中,作为1.1行为的第三个参数传递NULL,或者将指针传递给以下结构以指定其他信息:
typedef struct JavaVMAttachArgs {
jint version; /* must be 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
返回值:
成功返回“0”在失败时返回负数。
AttachCurrentThreadAsDaemon
jint AttachCurrentThreadAsDaemon(JavaVM * vm,void ** penv,void * args);
与AttachCurrentThread相同的语义,但新创建的java.lang.Thread实例是一个守护进程。
如果线程已经通过AttachCurrentThread或AttachCurrentThreadAsDaemon连接,则此例程将简单地将penv指向的值设置为当前线程的JNIEnv。在这种情况下,AttachCurrentThread和此例程都不会对线程的守护进程状态产生任何影响。
联系:
索引7在JavaVM界面功能表中。
参数:
vm:当前线程将附加到的虚拟机实例。
penv:指向当前线程的JNIEnv接口指针的位置的指针。
args:指向JavaVMAttachArgs结构的指针。
返回值:
成功返回零;否则返回一个负数。
异常:
没有。
SINCE:
JDK/JRE 1.4
DetachCurrentThread
jint DetachCurrentThread(JavaVM * vm);
从Java VM分离当前线程。此线程持有的所有Java监视器都将被释放。通知所有等待此线程死机的Java线程。
在JDK 1.1中,主线程不能与VM分离。它必须调用DestroyJavaVM来卸载整个VM。
在JDK中,主线程可以与VM分离。
主线程(即创建Java VM的线程)无法从VM分离。相反,主线程必须调用JNI_DestroyJavaVM()来卸载整个VM。
联系:
JavaVM界面功能表中的索引5。
参数:
vm:当前线程将从其中分离的VM。
返回值:
成功返回“0”在失败时返回负数。
GETENV
jint GetEnv(JavaVM * vm,void ** env,jint version);
联系:
索引6在JavaVM界面功能表中。
返回值:
如果当前线程未连接到VM,则将* env设置为NULL,并返回JNI_EDETACHED。如果不支持指定的版本,将* env设置为NULL,并返回JNI_EVERSION。否则,将* env设置到适当的接口,并返回JNI_OK。
SINCE:
JDK / JRE 1.2
- JDK 1.1不需要本地代码设置版本字段。为了向后兼容,如果版本字段未设置,JDK 1.1.2假定
所请求的版本为0x00010001。 JDK的未来版本将要求将版本字段设置为适当的值。
2.见脚注1。