您的位置:首页 > 编程语言 > Java开发

Java本地接口(JNI)编程指南和规范(第七章)

2011-12-17 17:40 429 查看
原文链接: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)顺序将可能不同,依赖于在线程安排中的随机因素。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  jni java 编程 solaris jvm jdk