Java本地接口(JNI)编程指南和规范(第七章)
原文链接:http://blog.sina.com.cn/s/blog_53988c0c0100oso3.html第七章 调用接口这章告诉你怎样能嵌入一个"Java"虚拟器到你的本地应用程序中。一个Java虚拟器实现是典型作为一个本地库的运用。本地应用程序能针对这个库链接和使用载入Java虚拟机的调用接口。真正地,在"JDK"或"Java 2 SDK release"中得标
原文链接:http://blog.sina.com.cn/s/blog_53988c0c0100oso3.html
第七章 调用接口
这章告诉你怎样能嵌入一个"Java"虚拟器到你的本地应用程序中。一个Java虚拟器实现是典型作为一个本地库的运用。本地应用程序能针对这个库链接和使用载入Java虚拟机的调用接口。真正地,在"JDK"或"Java 2 SDK release"中得标准的启动器命令(java)仅仅是一个链接到"Java"虚拟器上的简单C程序。这个启动器解析命令行参数,载入虚拟器,和通过调用接口来运行"Java"应用程序。
7.1 创建Java虚拟器
为了说明调用接口,让我们看一个"C"程序,它载入一个"Java"虚拟器和调用定义的"Prog.main"方法,如下:
public class Prog{
}
下面的"C"程序,"invoke.c",载入一个"Java"虚拟器和调用"Prog.main"。
#include <jni.h>
#define PATH_SEPERATOR
#define PATH_CLASSPATH
main(){
#ifdef JNI_VERSIO_1_2
#else
#endif
destroy:
}
这代码条件编译一个初始化结构"JDK1_1InitArgs",这结构明确虚拟器在"JDK release 1.1"上实现。"Java 2 SDK release 1.2"任然支持"JDK1_1InitArgs",虽然它介绍一个通用(general purpose)虚拟器初始化机构叫做"JavaVMInitArgs"。这个"JNI_VERSION_1_2"常数定义在"Java 2 SDK release 1.2"中,但不在"JDK release 1.1"中。
当目标是"1.1 release"时,"C"代码从调用"JNI_GetDefaultJavaVMInitArgs
当目标是"1.2 release"时,"C"代码创建了一个"JavaVMInitArgs"结构体。虚拟器初始参数被存储在"JavaVMOption"数组中。你能设置一般选项(例如(e.g.) -Djava.class.path=。)和实现的特别选项(例如(e.g.)"-Xmx64")来指示相应的"Java"命令行选项。设置"ignoreUnrecognized"域为"JNI_TRUE"命令虚拟器忽略不认识的特别实现选项。
在建立起虚拟器初始化结构后,"C"程序调用"JNI_CreateJavaVM"来载入和初始化"Java"虚拟器。这"JNI_CreateJavaVM"函数填入两个返回值:
.一个接口指针,"jvm",指向最新创建的"Java"虚拟器。
.为了当前线程的"JNIEnv"接口指针"env"。通过"env"接口指针,再调用本地代码访问"JNI"函数。
当"JNI_CreateJavaVM"函数成功返回时,当前本地线程已经引导(bootstrap)自己进入"Java"虚拟器。在这方面,就象运行一个本地方法。因此,在其它事中,能做出"JNI"调用来调用"Prog.main"方法。
最终(Eventually),程序调用"DestroyJavaVM"函数来载出Java虚拟器。(不幸地(Unfortunately),你不能载出Java虚拟器实现在"JDK release 1.1 or Java 2 SDK release 1.2"。"DestoryJavaVM"总是返回一个错误在这些版本(releases)中。)
运行上面程序产品:
Hello World from C!
7.2 链接本地应用程序和"Java"虚拟器(Linking Native Applications with the Java Virtual Machine)
调用接口请求你来链接程序例如"invoke.c"和一个"Java"虚拟器实现。你怎样和Java虚拟器链接,依赖于是否本地应用倾向于被布置到一个特别的虚拟器实现,或它被设计在来自不同厂商的不同虚拟器的实现上工作。
7.2.1 和一个知名的Java虚拟器链接
你可能决定你的本地应用程序将只被布置在一个特殊虚拟器实现上。在这种情况,你能链接本地应用程序到实现虚拟器的本地库上。例如,"Solaris的JDK 1.1 release"上,你能使用以下命令来编译和链接"invoke.c":
cc -I<jni.h dir> -L<libjava.so dir> -lthread -ljava invoke.c
"-lthread"选项表明我们使用Java虚拟器实现带有本地线程支持(8.1.5部分)。"-ljava"选项指明"libjava.so"是"Solaris"共享库,这共享库实现了"Java"虚拟器。
在Win32上带有的"Microsoft Visual C++"编译器,命令行来编译和链接同样代码和"JDK 1.1 release":
cl -I<jni.h dir> -MD invoke.c -link <javai.lib dir>\javai.lib
(其中 jni.h dir指的是jni.h的目录)
当然,你需要提供正确的头文件和库目录,它们目录是对应于在你机器上JDK安装目录。"-MD"选项确保你的本地应用程序被链接到"Win32"多线程"C"库上,同样的"C"库被在"JDK 1.1 and Java 2 SDK 1.2 releases"中的Java虚拟器使用。"cl"命令参考了"javai.lib"文件啊,在Win32上是"JDK release 1.1"导入的,是为了关于函数接口调用的链接信息,例如在虚拟器中的"JNI_CreateJavaVM"实现。在运行时被用的实际"JDK1.1"虚拟器实现包含在一个独立的动态链接库的"javai.dll"文件中。于此相反(In constrast),同样的Solaris系统的共享库文件(.so文件)也是在链接和运行时备用。
对于"Java 2 SDK release 1.2",虚拟器库名字在"Solaris"变为"libjvm.so",同时在Win32上变为"jvm.lib"和"jvm.dll"。总得来说,不同的供应商可以命名他们的不同的虚拟器实现。
一旦编译(compilation)和链接(linking)完成,你能从行命令运行可执行的(executable)文件(resulting)。你可能得到一个系统没有发现一个共享库或一个动态链接库的错误。在"Solaris"上,如果这个错误消息指示系统没有发现共享库"libjava.so"(或者"libjvm.so"在"Java 2 SDK release 1.2"上),然后你需要添加目录包含虚拟器库的目录到你的"LD_LIBRARY_PATH"变量中。在Win32系统,这个错误可能指示找不到动态链接库"javai.dll"(或"jvm.dll"在"Java 2 SDK release 1.2"中)。如果是这种情况,添加包含"DLL"的目录到你的PATH环境变量中。
7.2.2 和知名的Java虚拟器链接
如果应用程序倾向于和来自不同供应商的虚拟器的实现一起工作,你就不能链接本地应用程序和一个指定的实现了一个虚拟器的库。因为"JNI"不能详细说明实现一个"Java"虚拟器的本地库的名字,你应该准备使用不同名字发布的Java虚拟器实现。例如,在Win32上,虚拟器在JDK release 1.1中被作为"javai.dll"发布,在"Java 2 SDK release 1.2"中作为"jvm.dll"发布。
解决方法是使用运行时动态链接到(use run-time dynamic linking to)载入的指定的应用程序需要的虚拟器库。然后,虚拟器库的名字能被使用一种应用程序指定的方法来配置。例如, 下面的"Win32"代码找到被给一个虚拟器库的路径上的函数"JNI_CreateJavaVM"入口地址:
void *JNU_FindCreateJavaVM(char *vmlibpath)
{
}
"LoadLibrary"和"GetProcAddress"都是在Win32上的动态链接的API函数。虽然"LoadLibrary"能接受实现"Java"虚拟器的本地库的名字(例如"jvm")或路径(例如"C:\\jdk1.2\\jre\\bin\\classic\\jvm.dll"),最好是你传递一个本地库的绝对路径给"JNU_FindCreateJavaVM"函数。依赖于"LoadLibrary"来搜索"jvm.dll"文件,使你的应用程序很容易变化配置,例如添加到"PATH"环境变量。
"Solaris"本版是相似的:
void *JNU_FindCreateJavaaVM(char *vmlibpath)
{
}
"dlopen"和"dlsym"函数在“Solaris"上来支持动态链接的共享库。
7.3 附加本地线程(Attaching Native Threads)
假设你有个多线程的应用程序例如一个用"C"写的"web server"。当HTTP请求到来时,Web服务创建多个本地线程来处理并发的"HTTP"请求。我们想嵌入(embed)一个Java虚拟器在这个服务中,所以同时多线程能执行在虚拟器上的操作,如在"Figure 7.1"中的说明。
Figure 7.1 Embedding th Java virtual machine in a web server
服务器孵化的本地方法可能其生命比Java虚拟器还要短。因此,我们需要一个方法来附加一个本地线程到一个正在运行的Java虚拟器上,在这个被附加的本地线程上执行了"JNI"调用,然后在不破坏其他附加线程情况下从虚拟器分离本地线程。
接下来的例子,"attach.c",说明怎样附加本地线程到一个使用调用接口的虚拟器。这个程序使用"Win32"线程"API"来写的。相似的版本能被写为"Solaris"和其他操作系统。
#include <windows.h>
#include <jni.h>
JavaVM *jvm ;
#define PATH_SEPERATOR
#define PATH_CLASSPATH
void thread_fun(void *arg)
{
#ifdef JNI_VERSION_1_2
#else
#endif
detach:
}
main(){
#ifdef JNI_VERSION_1_2
#else
#endif
}
"attach.c"程序是一个"invoke.c"的变种。不是在主线程中调用"Prog.main"函数,而是本地代码启动了五个线程。一旦产生了线程,然后等待线程们都开始,再调用"DestroyJavaVM"。每个产生的线程都附加自己到"Java"虚拟器上,调用"Prog.main"方法, 同时最后在虚拟器终止前从虚拟器分离自己。在所有五个线程终止后,"DestroyJavaVM"返回。我们忽略"DestroyJavaVM"的返回值,因为在"JDK release 1.1 and Java 2 SDK release 1.2"中这个函数不能完整被执行。
"JNI_AttachCurrentThread"把NULL作为它的第三个参数。"Java 2 SDK release 1.2"介绍了"JNI_ThreadAttachArgs"机构体。允许你指定额外的参数,例如你想附加的线程组。"JNI_ThreadAttachArgs"机构体的细节作为"JNI_AttachCurrentThread"的定义的一部分被详细描述在13.2部分(section)。
当程序执行函数"DetachCurrentThread",它释放所有属于当前线程的局部引用。
运行程序产生下面输出:
Hello World from thread 1
Hello World from thread 0
Hello World from thread 4
Hello World from thread 2
Hello World from thread 3
输出的精确(exact)顺序将可能不同,依赖于在线程安排中的随机因素。
更多推荐
所有评论(0)