JNI(5)The Invocation API

时间:2022-08-28 22:04:51

调用API允许软件提供商加载Java VM 到任意的本地应用中。供应商可以提供支持Java的应用程序而无需链接Java VM的代码。

概述

下面代码展示了通过调用API如何使用函数。这个例子中C++代码创建了一个Java VM 和调用一个静态方法,方法为Main.test.为了代码简洁,省略了错误检查。

    #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, (void**)&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();

这个例子使用接口中的三个方法,调用API允许本地应用使用JNI接口指针去访问VM的特性。设计类似于网景的JRI嵌入接口。

创建 VM

JNI_CreateJavaVM()方法加载和初始化java VM和返回一个指向JNI接口指针的指针。JNI_CreateJavaVM()在主线程里面调用。

添加到 VM

JNI接口指针(JNIEnv)在当前线程中有效。如果另外一个线程要访问它,那么它必须先调用AttachCurrentThread()添加自己到VM中,并且得到JNI接口指针。一旦添加到VM,本地线程运行和java线程运行在本地方法一样。本地线程保持着VM的引用,知道调用DetachCurrentThread()方法,才会取消之间的关联关系。

附加的线程堆栈应该有足够的空间来执行一个合理数量的任务。操作系统给每个线程分配指定的空间。例如,使用线程,栈大小可以通过pthread_attr_t参数指定。

从VM中分离

一个本地线程要从VM中分离出来需要调用DetachCurrentThread(),分离必须是在退出之前。如果有java方法在栈中执行,那么线程不能自我分离。

卸载VM

JNI_DestroyJavaVM()卸载 Java VM. As of JDK/JRE 1.1, 只有主线程能够调用DestroyJavaVM卸载java VM. As of JDK/JRE 1.2, 可以在任何线程调用 DestroyJavaVM卸载 VM.

The VM waits until the current thread is the only non-daemon user thread before it actually unloads. User threads include both Java threads and attached native threads. This restriction exists because a Java thread or attached native thread may be holding system resources, such as locks, windows, and so on. The VM cannot automatically free these resources. By restricting the current thread to be the only running thread when the VM is unloaded, the burden of releasing system resources held by arbitrary threads is on the programmer.

库和版本管理

As of JDK/JRE 1.1, 一旦本地库加载, 所有的类加载器都能够看到它。因此不同的两个类在不同的类加载器中有可能链接的是同一个本地方法。这样会导致两个问题:

  • 一个类在不同的类加载器中可能会错误的链接本地库。
  • 本地方法可以很容混淆不同类加载器加载的类。这样打破了类加载器提供的命名空间分离,并且导致类型安全问题。

As of JDK/JRE 1.2, 每一个类加载器管理自己的本地库。同一个JNI本地库在一个类加载器中不能重复加载。如果那样做,会导致UnsatisfiedLinkError 抛出,例如,通常加载本地库到两个类装入器,System.loadLibrary 会抛出UnsatisfiedLinkError。这样做的好处是:

  • 基于类加载器的命名空间分离是保存在本地库里面。一个原生的库无法从不同的类加载器里面混淆类。
  • 此外,当他们对应的类加载器被垃圾回收之后也会卸载。

为了提高版本控制和资源管理,JNI库在JDK/JRE 1.2 里面提供了以下两个方法:

JNI_OnLoad

jint JNI_OnLoad(JavaVM *vm, void *reserved);

当原生库加载的时候,VM才调用JNI_OnLoad(例如,通过System.loadLibrary).JNI_OnLoad必须返回原生库需要的JNI版本。

要是新的JNI函数,原生库JNI_OnLoad函数必须返回JNI_VERSION_1_2。如果原生库没有JNI_OnLoad函数,那么VM会假定库需要的是JNI_VERSION_1_1. 如果VM不能识别JNI_OnLoad方法返回的值,那么原生库将不能被加载。

LINKAGE:

Exported from native libraries that contain native method implementation.

SINCE:

JDK/JRE 1.4

使用在J2SE1.2中介绍的JNI方法,除了JDK/JRE 1.1中的功能,原生包的JNI_OnLoad方法必须返回JNI_VERSION_1_2.

使用在J2SE1.4中介绍的JNI方法,除了JDK/JRE 1.2中的功能,原生包的JNI_OnLoad方法必须返回JNI_VERSION_1_4.

如果原生包没有JNI_OnLoad 函数, VM会认为包需要JNI的版本为JNI_VERSION_1_1.如果VM 不能识别JNI_OnLoad函数放回的版本号,那么原生包将不能被加载。

JNI_OnUnload

void JNI_OnUnload(JavaVM *vm, void *reserved);

VM在原生包被垃圾回收的时候调用JNI_OnUnload函数。这个函数可以用来执行清理操作。因为这个函数被一个不知道的上下文调用(比如从一个终结器),程序员应该保守使用Java VM服务,和避免任意Java回调。

注意:JNI onload和JNI onunload是两个函数出自JNI库,而不是VM。

LINKAGE:

Exported from native libraries that contain native method implementation.

Invocation API Functions

指针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之前就已经可以使用。

JNI_GetDefaultJavaVMInitArgs

jint JNI_GetDefaultJavaVMInitArgs(void *vm_args);

返回Java VM的默认配置。在使用这个函数之前,需要制定JNI版本的版本号( 设置这个属性vm_args->version),它表明VM需要的JNI版本号。这个方法返回之后,vm_args->version的值是VM支持的版本值。

LINKAGE:

Exported from the native library that implements the Java virtual machine.

PARAMETERS

vm_args: a pointer to a JavaVMInitArgs structure in to which the default arguments are filled.

RETURNS:

Returns JNI_OK if the requested version is supported; returns a JNI error code (a negative number) if the requested version is not supported.

JNI_GetCreatedJavaVMs

jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);

返回所有已经创建的Java vm。Returns all Java VMs that have been created. 所有的VM指针按照它们创建的顺序保存在vmBuf缓冲区中。VM 的总数是存放在*nVms 中。

在JDK / JRE 1.2, 在一个进程中创建多个VM是支持的。

LINKAGE:

Exported from the native library that implements the Java virtual machine.

PARAMETERS:

vmBuf: pointer to the buffer where the VM structures will be placed.

bufLen: the length of the buffer.

nVMs: a pointer to an integer.

RETURNS:

Returns JNI_OK on success; returns a suitable JNI error code (a negative number) on failure.

JNI_CreateJavaVM

jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args);

加载和初始化java VM. 当前线程成为主线程。设置主线程的JNI接口指针env参数。

在JDK/JRE 1.2 ,在一个进程中创建多个VM是支持的。

第二个参数JNI_CreateJavaVM 总是指向 JNIEnv *,而第三个参数是一个指针JavaVMInitArgs结构,使用选项字符串编码任意VM启动选项:

typedef struct JavaVMInitArgs {
jint version; jint nOptions;
JavaVMOption *options;
jboolean ignoreUnrecognized;
} JavaVMInitArgs;

version至少设置 JNI_VERSION_1_2. options 的类型是:

typedef struct JavaVMOption {
char *optionString; /* the option as a string in the default platform encoding */
void *extraInfo;
} JavaVMOption;

数组的大小是nOptions字段表示的。如果ignoreUnrecognized 为 JNI_TRUEJNI_CreateJavaVM 忽略所有以 "-X" 或者 "_"开头的不能识别的选项字符串。 如果ignoreUnrecognized 为JNI_FALSEJNI_CreateJavaVM 遇到不能识别的字符串会立刻返回JNI_ERR 。所有的java VMs必须都识别下面的标准的参数:

optionString meaning
-D<name>=<value> 设置一个系统属性
-verbose[:class|gc|jni] 启用详细的输出。 The options can be followed by a comma-separated list of names indicating what kind of messages will be printed by the VM. For example, "-verbose:gc,class" instructs the VM to print GC and class loading related messages. Standard names include: gcclass, and jni. All nonstandard (VM-specific) names must begin with "X".
vfprintf extraInfo is a pointer to the vfprintf hook.
exit extraInfo is a pointer to the exit hook.
abort extraInfo is a pointer to the abort hook.

此外,每个VM实现可能会支持自己的一套标准选项字符串。非标准的选项名称必须开始以“- x”或下划线(“_”)。例如,JDK/JRE 支持-Xms 和-Xmx 允许指定初始的和最大的堆内存大小。以"-X"的选项都可以在java命令行中运行。

下面是示例代码,创建一个Java VM在JDK / JRE:

JavaVMInitArgs vm_args;
JavaVMOption options[4]; options[0].optionString = "-Djava.compiler=NONE"; /* disable JIT */
options[1].optionString = "-Djava.class.path=c:\myclasses"; /* user classes */
options[2].optionString = "-Djava.library.path=c:\mylibs"; /* set native library path */
options[3].optionString = "-verbose:jni"; /* print JNI-related messages */ vm_args.version = JNI_VERSION_1_2;
vm_args.options = options;
vm_args.nOptions = 4;
vm_args.ignoreUnrecognized = TRUE; /* Note that in the JDK/JRE, there is no longer any need to call
* JNI_GetDefaultJavaVMInitArgs.
*/
res = JNI_CreateJavaVM(&vm, (void **)&env, &vm_args);
if (res < 0) ...

LINKAGE:

Exported from the native library that implements the Java virtual machine.

PARAMETERS:

p_vm: VM 结构的地址.

p_env: JNI接口的地址(主线程中).

vm_args: VM初始化参数.

RETURNS:

Returns JNI_OK on success; returns a suitable JNI error code (a negative number) on failure.

DestroyJavaVM

jint DestroyJavaVM(JavaVM *vm);

卸载一个Java VM并收回其资源。

The support for DestroyJavaVM was not complete in JDK/JRE 1.1. As of JDK/JRE 1.1 Only the main thread may call DestroyJavaVM.Since JDK/JRE 1.2, any thread, whether attached or not, can call this function. If the current thread is attached, the VM waits until the current thread is the only non-daemon user-level Java thread. If the current thread is not attached, the VM attaches the current thread and then waits until the current thread is the only non-daemon user-level thread.

JDK / JRE 1.1不完全支持DestroyJavaVM. 在JDK/JRE 1.1 只有主线程可以叫DestroyJavaVM。 从JDK/JRE 1.2, 任何线程都可以调用这个函数, 不管是否从依附的线程中. 如果与当前线程连接, VM等到当前线程是唯一非守护进程。如果没有与当前线程相连接,那么VM会与当前线程相连,并且等待当前线程是非守护线程. The JDK/JRE still does not support VM unloading, however.

LINKAGE:

Index 3 in the JavaVM interface function table.

PARAMETERS:

vm: the Java VM that will be destroyed.

RETURNS:

Returns JNI_OK on success; returns a suitable JNI error code (a negative number) on failure.

在JDK / JRE 1.1.2卸载虚拟机不支持。

AttachCurrentThread

jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args);

Attaches the current thread to a Java VM. Returns a JNI interface pointer in the JNIEnv argument.

Trying to attach a thread that is already attached is a no-op.

A native thread cannot be attached simultaneously to two Java VMs(本机线程不能同时与两个Java虚拟机连接。).

When a thread is attached to the VM, the context class loader is the bootstrap loader.

LINKAGE:

Index 4 in the JavaVM interface function table.

PARAMETERS:

vm: the VM to which the current thread will be attached.

p_env: pointer to the location where the JNI interface pointer of the current thread will be placed.

thr_args: can be NULL or a pointer to a JavaVMAttachArgs structure to specify additional information:

As of JDK/JRE 1.1, the second argument to AttachCurrentThread is always a pointer to JNIEnv. The third argument to AttachCurrentThread was reserved, and should be set to NULL.

As of JDK/JRE 1.2, you pass NULL as the third argument for 1.1 behavior, or pass a pointer to the following structure to specify additional information:

typedef struct JavaVMAttachArgs {
jint version; /* must be at least 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

RETURNS:

Returns JNI_OK on success; returns a suitable JNI error code (a negative number) on failure.

AttachCurrentThreadAsDaemon

jint AttachCurrentThreadAsDaemon(JavaVM* vm, void** penv, void* args);

Same semantics as AttachCurrentThread, but the newly-created java.lang.Thread instance is a daemon.

If the thread has already been attached via either AttachCurrentThread or AttachCurrentThreadAsDaemon, this routine simply sets the value pointed to bypenv to the JNIEnv of the current thread. In this case neither AttachCurrentThread nor this routine have any effect on the daemon status of the thread.

LINKAGE:

Index 7 in the JavaVM interface function table.

PARAMETERS:

vm: the virtual machine instance to which the current thread will be attached.

penv: a pointer to the location in which the JNIEnv interface pointer for the current thread will be placed.

args: a pointer to a JavaVMAttachArgs structure.

RETURNS

Returns JNI_OK on success; returns a suitable JNI error code (a negative number) on failure.

EXCEPTIONS

None.

SINCE:

JDK/JRE 1.4

DetachCurrentThread

jint DetachCurrentThread(JavaVM *vm);

Detaches the current thread from a Java VM. All Java monitors held by this thread are released. All Java threads waiting for this thread to die are notified.

As of JDK/JRE 1.2 , the main thread can be detached from the VM.

LINKAGE:

Index 5 in the JavaVM interface function table.

PARAMETERS:

vm: the VM from which the current thread will be detached.

RETURNS:

Returns JNI_OK on success; returns a suitable JNI error code (a negative number) on failure.

GetEnv

jint GetEnv(JavaVM *vm, void **env, jint version);

LINKAGE:

Index 6 in the JavaVM interface function table.

PARAMETERS:

vm: The virtual machine instance from which the interface will be retrieved.
env: pointer to the location where the JNI interface pointer for the current thread will be placed.
version: The requested JNI version.

RETURNS:

If the current thread is not attached to the VM, sets *env to NULL, and returns JNI_EDETACHED. If the specified version is not supported, sets *env to NULL, and returns JNI_EVERSION. Otherwise, sets *env to the appropriate interface, and returns JNI_OK.

SINCE:

JDK/JRE 1.2