原文链接: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{
 public static void main(String[] args){
  System.out.println("Hello World" + args[0]) ;
 }
}

 

下面的"C"程序,"invoke.c",载入一个"Java"虚拟器和调用"Prog.main"。
#include <jni.h>

#define PATH_SEPERATOR ';' 
#define PATH_CLASSPATH '.' 

main(){
 JNIEnv *env ;
 JavaVM *jvm ;
 jint res ;
 jclass cls ;
 jmethodID mid ;
 jstring jstr ;
 jclass stringClass ;
 jobjectArray args ;

#ifdef JNI_VERSIO_1_2
 JavaVMInitArgs vm_args ;
 JavaVMOption options[1] ;
 options[0].optionString = "-Djava.class.path="USERCLASSPATH ;
 vm_args.version = 0x00010002 ;
 vm_args.options = options ;
 vm_args.ignoreUnrecognized= JNI_TRUE ;
 
 res = JNI_CreateJavaVM(&jvm, (Void **)&env, &vm_args) ;
#else
 JDK1_1InitArgs vm_args ;
 char classpath[1024] ;
 vm_args.version = 0x00010001 ;
 JNI_GetDefaultJavaVMInitArgs(&vm_args) ;
 
 sprintf(classpath, "%s%c%s",
  vm_args.classpath, PATH_SEPERATOR, USER_CLASSPATH) ;
 vm_args.classpath = classpath ;
 
 res = JNI_CreateJavaVM(&jvm, &env, &vm_args) ;
#endif 

 if ( res < 0 ){
  fprintf(stderr, "Can't create Java VM\n") ;
  exit(1) ;
 }
 cls = (*env)->FindClass(env, "Prog") ;
 if ( cls == NULL ){
  goto destroy ;
 }

 mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V") ;
 if ( mid == NULL ){
  goto destory ;
 }
 
 jstr = (*env)->NewStringUTF(env, " From C!") ;
 if( jstr == NULL ){
  goto destory ;
 }
 stringClass = (*env)->FindClass(env,"java/lang/String") ;
 args = (*env)->NewObjectArray(env, 1, stringClass, jstr) ;
 if( args == NULL ){
  goto destory ;
 }
 (*env)->CallStaticVoidMethod(env, cls, mid, args) ;

destroy:
 if( (*env)->ExceptionOccurred(env) ){
  (*env)->ExceptionDescribe(env) ;
 }
 (*jvm)->DestroyJavaVM(jvm) ;
}

 

这代码条件编译一个初始化结构"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"得到虚拟器设定开始。"JNI_GetDefaultJavaVMInitArgs"返回值包含堆的大小,栈大小,默认类路经等等(and so on)在"vm_args"参数中。然后我们追加"Prog.class"所在的目录到"vm_args.classpath"结尾。

 

当目标是"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)
{
 HINSTANCE hVM = LoadLibrary(vmlibpath) ;
 if ( hVM == NULL ){
  return NULL ;
 }
 return GetProcAddress(hVM, "JNI_CreateJavaVM") ;
}

 

"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)
{
 void *libVM = dlopen(vmlibpath, RTLD_LAZY);
 if( libVM == NULL ){
  return NULL ;
 }
 return dlsym(libVM, "JNI_CreateJavaVM") ;
}

 

"dlopen"和"dlsym"函数在“Solaris"上来支持动态链接的共享库。

 

7.3 附加本地线程(Attaching Native Threads)
假设你有个多线程的应用程序例如一个用"C"写的"web server"。当HTTP请求到来时,Web服务创建多个本地线程来处理并发的"HTTP"请求。我们想嵌入(embed)一个Java虚拟器在这个服务中,所以同时多线程能执行在虚拟器上的操作,如在"Figure 7.1"中的说明。


                ---->
  HTTP requests ..... Web server written in C
                ---->    |   |      |
                         |   | .... |
    Server-spawned   <---|---|------|
    native thread
  _______________________   |?|
  |      | JNI |        | <-|-|
  |Java virtual machine |
  -----------------------
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)
{
 jint res ;
 jclass cls ;
 jmethodID mid ;
 jstring jstr ;
 jclass stringClass ;
 jobjectArray args ;
 JNIEnv *env
 char buf[100] ;
 int threadNm = (int)arg ;

 
#ifdef JNI_VERSION_1_2
 res = (*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL ) ;
#else
 res = (*jvm)->AttachCurrentThread(jvm, &env, NULL) ;
#endif

 if( res < 0 ){
  fprintf(stderr, "Attach failed\n") ;
  retrn ;
 }

 cls = (*env)->FindClass(env, "Prog") ;
 if ( cls == NULL ){
  goto detach
 }

 mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V") ;
 if ( mid == NULL ){
  goto detach ;
 }
 
 sprintf(buf, " from Thread %d", threadNum) ;
 jstr = (*env)->NewStringUTF(env, buf) ;
 if( jstr == NULL ){
  goto detach ;
 }
 
 stringClass = (*env)->FindClass(env, "java/lang/String") ;
 args = (*env)->NewObjectArray(env, 1, stringClass, jstr) ;
 if ( args == NULL ){
  goto detach ;
 }
 
 (*env)->CallStaticVoidMethod(env, cls, mid , args) ;

detach:
 if( (*env)->ExceptionOccurred(env)){
  (*env)->ExceptionDescribe(env) ;
 }
 (*jvm)->DetachCurrentThread(jvm) ;
}

main(){
 JNIEnv *env ;
 int i ;
 jint res ;
#ifdef JNI_VERSION_1_2
 JavaVMInitArgs vm_args ;
 JavaVMOption options[1] ;
 
 options[0].option.String = "-Djava.class.path="USER_CLASSPATH ;
 vm_args.version = 0x00010002 ;
 vm_args.options = options ;
 vm_args.nOptions = 1 ;
 vm_args.ignoreUnrecognized = TRUE ;

 
 res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args) ;
#else
 JDK1_1InitArgs vm_args ;
 char classpatch[1024] ;
 
 vm_args.version = 0x00010001 ;
 JNI_GetDefaultJavaVMInitArgs(&vm_args) ;
 
 sprintf(classpath, "%s%c%s",
  vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH) ;
 vm_args.classpath = classpath ;
 
 res = JNI_CreateJavaVM(&jvm, &env, &vm_args) ;
#endif

 if( res < 0 ){
  fprintf(stderr, "Can't create Java VM\n") ;
  exit(1) ;
 }

 for ( i = 0 ; i < 5 ; i++ )
  
  _beginthread(thread_fun, 0, (void *) i ) ;
 sleep(1000) ;
 (*jvm)->DestroyJavaVM(jvm) ;
}

 

"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)顺序将可能不同,依赖于在线程安排中的随机因素。


Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐