新手学做网站的书,网站开发项目思路,中信建设有限责任公司是国企还是央企,网站的动态图怎么做的上一篇#xff1a; 04-JNI函数 调用 API 允许软件供应商将 Java VM 加载到任意本地应用程序中。供应商可以提供支持 Java 的应用程序#xff0c;而无需链接 Java VM 源代码。
5.1 概述 下面的代码示例说明了如何使用调用 API 中的函数。在这个示例中#xff0c;C 代码创建了…上一篇 04-JNI函数 调用 API 允许软件供应商将 Java VM 加载到任意本地应用程序中。供应商可以提供支持 Java 的应用程序而无需链接 Java VM 源代码。
5.1 概述 下面的代码示例说明了如何使用调用 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 19 VM initialization arguments */
JavaVMOption* options new JavaVMOption[1];
options[0].optionString -Djava.class.path/usr/lib/java;
vm_args.version JNI_VERSION_19;
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 中的两个函数。调用 API 允许本地应用程序使用 JNI 接口指针访问虚拟机功能。
5.1.1 创建虚拟机 JNI_CreateJavaVM() 函数加载并初始化 Java VM并返回一个指向 JNI 接口指针的指针。调用 JNI_CreateJavaVM() 的线程被视为主线程并连接到 Java VM。 注意根据操作系统的不同原始进程线程可能会受到特殊处理从而影响其作为正常 Java 线程正常运行的能力如堆栈大小受限和可以抛出 StackOverflowError 。强烈建议不要使用原始线程加载 Java VM而应专门为此创建一个新线程。
5.1.2 附加到虚拟机 JNI 接口指针 JNIEnv 只在当前线程中有效。如果另一个线程需要访问 Java 虚拟机它必须首先调用 AttachCurrentThread() 将自己附加到虚拟机并获取 JNI 接口指针。附加到虚拟机后本地线程的工作方式与运行在本地方法中的普通 Java 线程无异唯一的例外是在调用对调用者敏感的方法时没有 Java 调用者。本地线程会一直附着在虚拟机上直到调用 DetachCurrentThread() 将自己分离。 附加线程应有足够的堆栈空间来执行合理的工作。每个线程的堆栈空间分配取决于操作系统。例如使用 pthreads 时可以在 pthread_create 的 pthread_attr_t 参数中指定堆栈大小。
5.1.3 脱离虚拟机 连接到虚拟机的本地线程必须在终止前调用 DetachCurrentThread() 来自行分离。如果调用栈上有 Java 方法线程则无法自行退出。
5.1.4 终止虚拟机 DestroyJavaVM() 功能可终止 Java 虚拟机。 该函数会等到没有非守护进程线程执行时才实际终止虚拟机。非守护进程线程包括 Java 线程和附加的本地线程。等待的原因是非守护进程线程可能会占用系统资源如锁或窗口。Java 应用程序或附加本地代码的程序员有责任在线程终止或分离前释放这些资源。虚拟机无法自动释放这些资源因此会等待程序员在终止前释放这些资源。
5.2 库和版本管理 本地程序库可以与虚拟机动态链接或静态链接。库与虚拟机映像的结合方式取决于实现。必须使用 System.loadLibrary 或等效的 API 才能将库视为已加载这既适用于动态链接的库也适用于静态链接的库。 本地库一旦加载所有类加载器都能看到它。因此不同类加载器中的两个类可能会链接到同一个本地方法。这会导致两个问题 ①. 一个类可能会错误地与不同类加载器中同名类加载的本地库链接。 ②. 本地方法很容易混合来自不同类加载器的类。这会破坏类加载器提供的名称空间分隔并导致类型安全问题。 每个类加载器都管理自己的本地库。同一个 JNI 本地库不能加载到多个类加载器中。这样做会导致 UnsatisfiedLinkError 抛出。例如当使用 System.loadLibrary 将一个本地库加载到两个类加载器时就会抛出 UnsatisfiedLinkError 。这种方法的优点是 ①. 本地程序库中保留了基于类加载器的名称空间分隔。本地库不能轻易混合来自不同类加载器的类。 ②. 此外当相应的类加载器被垃圾回收时本地库也可以被卸载。
5.2.1 支持静态链接库 以下规则适用于静态链接库这些示例中给出的静态链接库名为 L ①. 当且仅当库导出一个名为 JNI_OnLoad_L的函数时其映像已与虚拟机结合的库 L 才被定义为静态链接库。 ②. 如果静态链接库 L 输出了一个名为 JNI_OnLoad_L 的函数和一个名为 JNI_OnLoad 的函数那么 JNI_OnLoad 函数将被忽略。 ③. 如果函数库 L 是静态链接的那么在首次调用 System.loadLibrary(L) 或等效 API 时将调用一个 JNI_OnLoad_L 函数其参数和预期返回值与为 JNI_OnLoad函数指定的参数和预期返回值相同。 ④. 静态链接的库 L 将禁止动态加载同名库。 ⑤. 当包含静态链接本地程序库 L 的类加载器被垃圾回收时如果程序库导出了 JNI_OnUnload_L函数虚拟机将调用该函数。 ⑥. 如果静态链接库 L 输出了一个名为 JNI_OnUnload_L 的函数和一个名为 JNI_OnUnload 的函数那么 JNI_OnUnload 函数将被忽略。 程序员还可以调用 JNI 函数 RegisterNatives() 来注册与类相关的本地方法。 RegisterNatives() 函数对静态链接函数特别有用。 如果动态链接库定义了 JNI_OnLoad_L 和/或JNI_OnUnload_L函数这些函数将被忽略。
5.2.2 库钩子函数的生命周期 为了便于版本控制和资源管理JNI 库可以定义加载和卸载函数钩子。这些函数的命名取决于库是动态链接还是静态链接。
5.2.3 JNI_OnLoad
/*** brief 动态链接库定义的可选函数。* 虚拟机在加载本地库时调用 JNI_OnLoad 例如通过 System.loadLibrary * * param vm 指向当前虚拟机结构的指针* param reserved 未使用的指针* return jint 返回所需的 JNI_VERSION 常量另见 GetVersion */
jint JNI_OnLoad(JavaVM *vm, void *reserved); 为了使用在 JNI API 的某个版本中定义的函数 JNI_OnLoad 必须返回一个至少定义了该版本的常量。例如希望使用 JDK 1.4 引入的1 个函数的库至少需要返回 JNI_VERSION_1_4 个常量。如果本地库没有导出 JNI_OnLoad 个函数虚拟机就会认为该库只需要 JNI 版本 JNI_VERSION_1_1 。如果虚拟机无法识别 JNI_OnLoad 所返回的版本号虚拟机将卸载该库就像从未加载过该库一样。 从包含本地方法实现的动态链接本地库导出。
5.2.4 JNI_OnUnload
/*** brief 动态链接库定义的可选函数。* 当包含本地库的类加载器被垃圾回收时虚拟机会调用 JNI_OnUnload 。** param vm 指向当前虚拟机结构的指针* param reserved 未使用的指针*/
void JNI_OnUnload(JavaVM *vm, void *reserved);该函数可用于执行清理操作。由于该函数是在未知上下文如来自 finalizer中调用的因此程序员在使用 Java VM 服务时应保持谨慎避免任意回调 Java 服务。 从包含本地方法实现的动态链接本地库导出。
5.2.5 JNI_OnLoad_L
/*** brief 静态链接库必须定义的强制函数** param vm 指向当前虚拟机结构的指针* param reserved 未使用的指针* return jint 返回所需的 JNI_VERSION 常量另见 GetVersion 。返回的最小版本至少为 JNI_VERSION_1_8* since JDK/JRE 1.8*/
jint JNI_Onload_L(JavaVM *vm, void *reserved);如果一个名为 L 的库是静态链接的那么在首次调用 System.loadLibrary(L) 或等效 API 时将调用一个 JNI_OnLoad_L 函数其参数和预期返回值与为 JNI_OnLoad 函数指定的参数和预期返回值相同。 JNI_OnLoad_L 必须返回本地库所需的 JNI 版本。该版本必须是 JNI_VERSION_1_8 或更高版本。如果虚拟机无法识别 JNI_OnLoad_L 所返回的版本号虚拟机将把该库当作从未加载过。
5.2.6 JNI_OnUnload_L
/*** brief 静态链接库定义的可选函数。* 当包含静态链接本地程序库 L 的类加载器被垃圾回收时如果程序库导出了 JNI_OnUnload_L 函数虚拟机将调用该函数。* param vm 指向当前虚拟机结构的指针* param reserved 未使用的指针*/
void JNI_OnUnload_L(JavaVM *vm, void *reserved); 该函数可用于执行清理操作。由于该函数是在未知上下文如来自 finalizer中调用的因此程序员在使用 Java VM 服务时应保持谨慎避免任意回调 Java 服务。 从包含本地方法实现的静态链接本地库中导出。 信息说明 加载本地库的行为是使 Java 虚拟机和运行时了解并注册该库及其本地入口点的完整过程。请注意仅仅执行操作系统级别的操作来加载本地库如在 UNIX(R) 系统上执行 dlopen 操作并不能完全实现这一目标。本机函数通常由 Java 类加载器调用以执行对主机操作系统的调用将库加载到内存中并返回本机库的句柄。该句柄将被存储并用于后续本地库入口点的搜索。一旦句柄成功返回Java 本地类加载器将完成加载过程并注册该库。 5.3 调用 API 函数 JavaVM 类型是指向调用 API 函数表的指针。下面的代码示例显示了该函数表
typedef const struct JNIInvokeInterface *JavaVM;const struct JNIInvokeInterface ... {NULL,NULL,NULL,DestroyJavaVM,AttachCurrentThread,DetachCurrentThread,GetEnv,AttachCurrentThreadAsDaemon
};请注意 JNI_GetDefaultJavaVMInitArgs() 、 JNI_GetCreatedJavaVMs() 和 JNI_CreateJavaVM() 这三个调用 API 函数不在 JavaVM 函数表中。这些函数可以在没有预先存在的 JavaVM 结构的情况下使用。
5.3.1 JNI_GetDefaultJavaVMInitArgs
/*** brief 返回 Java 虚拟机的默认配置。* 调用此函数前本地代码必须将 vm_args-version 字段设置为它期望虚拟机支持的 JNI 版本。* 此函数返回后vm_args-version 将被设置为 VM 实际支持的 JNI 版本。** param vm_args 指向 JavaVMInitArgs 结构的指针缺省参数填入该结构中不能是 NULL 结构* return jint 如果请求的版本受支持则返回 JNI_OK 如果请求的版本不受支持则返回 JNI 错误代码负数。*/
jint JNI_GetDefaultJavaVMInitArgs(void *vm_args);从实现 Java 虚拟机的本地库中导出。
5.3.2 JNI_GetCreatedJavaVMs
/*** brief 返回已创建的所有 Java 虚拟机。* 虚拟机的指针将按照创建顺序写入缓冲区 vmBuf 。最多写入1 个条目。* 创建的虚拟机总数将在 \*nVMs 中返回。** param vmBuf 指向放置虚拟机结构的缓冲区的指针不得为 NULL* param bufLen 缓冲区的长度* param nVMs 整数指针。可为 NULL 值* return jint 成功时返回 JNI_OK 失败时返回合适的 JNI 错误代码负数。*/
jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);5.3.3 JNI_CreateJavaVM
/*** brief 加载并初始化 Java VM。* 当前线程将连接到 Java VM 并成为主线程。将 p_env 参数设置为主线程的 JNI 接口指针。** param p_vm 指针指向放置生成的虚拟机结构的位置。不能为 NULL* param p_env 指向主线程 JNI 接口指针位置的指针。不能为 NULL* param vm_args Java VM 初始化参数。不能为 NULL* return jint 成功时返回 JNI_OK 失败时返回合适的 JNI 错误代码负数。*/
jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args);不支持在单个进程中创建多个虚拟机。 JNI_CreateJavaVM 的第二个参数始终是指向 JNIEnv * 的指针而第三个参数是指向 JavaVMInitArgs 结构的指针该结构使用选项字符串对任意虚拟机启动选项进行编码
typedef struct JavaVMInitArgs {jint version;jint nOptions;JavaVMOption *options;jboolean ignoreUnrecognized;
} JavaVMInitArgs;options 字段是以下类型的数组
typedef struct JavaVMOption {char *optionString; /* the option as a string in the default platform encoding */void *extraInfo;
} JavaVMOption;数组的大小由 JavaVMInitArgs 中的 nOptions 字段表示。如果 ignoreUnrecognized 为 JNI_TRUE JNI_CreateJavaVM 会忽略所有以 -X 或 _ 开头的未识别选项字符串。如果 ignoreUnrecognized 为 JNI_FALSE JNI_CreateJavaVM 在遇到任何未识别的选项字符串时立即返回 JNI_ERR 。所有 Java 虚拟机都必须识别以下一组标准选项 与模块相关的选项 --add-reads 、 --add-exports 、 --add-opens 、 --add-modules 、 --limit-modules 、 --module-path 、 --patch-module 和 --upgrade-module-path 必须以选项字符串形式传递使用 optionvalue 格式而不是 option value 格式(注意option 和 value 之间必须有 。例如要将 java.management/sun.management 输出为 ALL-UNNAMED 必须传递选项字符串 --add-exportsjava.management/sun.managementALL-UNNAMED 。 此外每个虚拟机实现都可能支持自己的一套非标准选项字符串。非标准选项名称必须以 -X 或下划线 _ 开头。例如JDK/JRE 支持 -Xms 和 -Xmx 选项允许程序员指定初始堆大小和最大堆大小。以 -X 开头的选项可以通过 java 命令行访问。 下面是在 JDK/JRE 中创建 Java VM 的示例代码
JavaVMInitArgs vm_args;
JavaVMOption options[3];options[0].optionString -Djava.class.pathc:\myclasses; /* user classes */
options[1].optionString -Djava.library.pathc:\mylibs; /* set native library path */
options[2].optionString -verbose:jni; /* print JNI-related messages */vm_args.version JNI_VERSION_1_2;
vm_args.options options;
vm_args.nOptions 3;
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) ...5.3.4 DestroyJavaVM
/*** brief 终止 Java 虚拟机的运行尽力释放虚拟机资源。* JavaVM 接口函数表中的索引 3** param vm 将被销毁的 Java 虚拟机。不能为 NULL* return jint 成功时返回 JNI_OK 失败时返回合适的 JNI 错误代码负数*/
jint DestroyJavaVM(JavaVM *vm);任何线程无论是否已连接都可以调用此函数。如果当前线程尚未附加则首先附加该线程。如果当前线程已被附加那么如果它的调用栈中有任何 Java 方法则会出错。 该函数等待所有非守护进程线程如果当前线程为非守护进程线程则不包括该线程都已终止然后启动关闭序列请参阅 java.lang.Runtime。关闭序列结束时Java VM 将终止导致任何仍在执行 Java 代码的线程停止执行该代码并释放其能够释放的任何相关 VM 资源。此时当前线程不再依附于 Java VM此函数返回给调用者。 请注意Java VM 终止时仍在执行本地代码的任何线程都将继续执行该代码但如果它们试图恢复执行 Java 代码则将停止执行。这包括守护进程线程和关闭序列启动后启动的任何非守护进程线程。守护进程线程和非守护进程线程仅对附加的本地线程有意义非附加的本地线程不受 Java VM 终止的影响。
5.3.5 AttachCurrentThread
/*** brief 将当前线程作为非守护进程线程附加到 Java 虚拟机。在 p_env 参数中返回一个 JNI 接口指针* JavaVM 接口函数表中的索引 4** param vm 当前线程将连接的虚拟机必须不是 NULL* param p_env 指向当前线程的 JNI 接口指针所在位置的指针。不能为 NULL* param thr_args 可以是 NULL 或指向 JavaVMAttachArgs 结构的指针用于指定附加信息* return jint 成功时返回 JNI_OK 失败时返回合适的 JNI 错误代码负数*/
jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args);尝试附加已附加的线程时只会在 p_env 参数中返回其现有的 JNI 接口指针。调用此方法后已附加线程的守护进程状态将保持不变。 一个本地线程不能同时连接到两个 Java 虚拟机。 当线程连接到虚拟机时上下文类加载器就是引导加载器。 thr_args可以是 NULL 或指向 JavaVMAttachArgs 结构的指针用于指定附加信息
typedef struct JavaVMAttachArgs {jint version;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
5.3.6 AttachCurrentThreadAsDaemon
/*** brief 将当前线程作为守护线程附加到 Java VM。在 p_env 参数中返回一个 JNI 接口指针* JavaVM 接口函数表中的索引 7** param vm 当前线程将连接的虚拟机实例。不能为 NULL* param p_env 指向当前线程 JNIEnv 接口指针所在位置的指针。它不能是 NULL* param thr_args 可以是 NULL 或指向 JavaVMAttachArgs 结构的指针用于指定附加信息* return jint 成功时返回 JNI_OK 失败时返回合适的 JNI 错误代码负数*/
jint AttachCurrentThreadAsDaemon(JavaVM *vm, void **p_env, void *thr_args);尝试附加已附加的线程时只会在 p_env 参数中返回其现有的 JNI 接口指针。调用此方法后已附加线程的守护进程状态将保持不变。 一个本地线程不能同时连接到两个 Java 虚拟机。 当线程连接到虚拟机时上下文类加载器就是引导加载器。 thr_args 可以是 NULL 或指向 JavaVMAttachArgs 结构的指针用于指定附加信息
typedef struct JavaVMAttachArgs {jint version;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
5.3.7 DetachCurrentThread
/*** brief 将当前线程从 Java VM 中分离。如果调用栈中有 Java 方法则线程无法脱离。* JavaVM 接口函数表中的索引 5** param vm 当前线程将脱离的虚拟机。必须不是 NULL* return jint 成功时返回 JNI_OK 失败时返回合适的 JNI 错误代码负数*/
jint DetachCurrentThread(JavaVM *vm);该线程仍持有的任何 Java 监视器都会被释放不过在正确编写的程序中所有监视器都会在此之前被释放。现在该线程被视为已终止不再存活所有等待该线程死亡的 Java 线程都会收到通知。 主线程可以从虚拟机中分离出来。 试图分离未连接的线程是不可能的。 如果调用 DetachCurrentThread 时有异常等待处理虚拟机可以选择报告该异常的存在。
5.3.8 GetEnv
/*** brief JavaVM 接口函数表中的索引 6** param vm 将从中检索接口的虚拟机实例。不得为 NULL* param p_env 指向当前线程的 JNI 接口指针所在位置的指针。不能为 NULL* param version 请求的 JNI 版本* return jint 如果当前线程未连接到虚拟机则将 *env 设为 NULL 并返回 JNI_EDETACHED 。* 如果不支持指定的版本则将 *env 设置为 NULL 并返回 JNI_EVERSION 。* 否则将 *env 设置为相应的接口并返回 JNI_OK*/
jint GetEnv(JavaVM *vm, void **p_env, jint version);