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

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

Java本地接口(JNI)编程指南和规范(第六章)
2011年07月26日
  Java本地接口(JNI)编程指南和规范(第六章)第六章 异常(CHAPTER 6 Exceptions)
  在调用JNI函数后,在本地代码为可能出现的错误做检查中,我们遇到的许多情况。这章探讨本地代码怎样侦测和修复这些错误情况。
  我们将关注作为"JNI"函数调用的结果的发生的错误,不是在本地代码中发生的任何错误(arbitrary errors)。如果一个本地调用操作系统功能,这只能简单使用记录文本的方法来在系统调用中可能的失败。另一方面,如果本地方法调用一个"Java API"方法的回调函数,使用在这章中详细描述的几步来恰当地检查和修复在方法执行中发生的可能的一场。
  6.1 概要(Overview)
  通过一系列例子,我们介绍"JNI"异常处理函数。
  6.1.1 在本地方法中缓冲和抛出异常
  下面的程序显示怎样声明一个抛出一个异常的本地方法。"CatchThrow"类声明一个"doit"本地方法,同时指定他抛出一个"IllegalArgumentException":
  class CathcThrow{
  private native void doit()
  thows IllegalArgumentException;
  private void callback() throw NulllPointerException{
  throw new NullPointerException("CathThrow.callback") ;
  }
  public static void main(String args[]){
  CatchThrow c = new CatchThrow() ;
  try{
  c.doit() ;
  }catch(Exception e){
  System.out.println("In Java: \n\t"+e) ;
  }
  }
  static{
  System.loadLibrary("CatchThrow") ;
  }
  }
  "CatchThrow.main"方法调用本地函数"doit",实现如下:
  JNIEXPORT void JNICALL
  Java_CatchThrow_doit(JNIEnv *env, jobject obj)
  {
  jthrowable exc ;
  jclass cls = (*env)->GetObjectClass(env, obj) ;
  jmethodID mid =
  (*env)->GetMethodID(env, cls, "callback", "()V") ;
  if ( mid == NULL ){
  return ;
  }
  (*env)->CallVoidMethod(env, obj, mid) ;
  exc = (*env)->ExceptionOccurred(env) ;
  if (exc){
  jclass newExcCls ;
  (*env)->ExceptionDescribe(env) ;
  (*env)->ExceptionClear(env) ;
  newExcCls = (*env)->FindClass(env,
  "java/lang/IllegalArgumentException");
  if( newExcCls == NULL ){
  return ;
  }
  (*env)->ThrowNew(env, newExCls, "thrown from C code") ;
  }
  }
  运行带本地库程序,如下输出:
  java.lang.NullPointerException:
  at CatchThrow.callback(CatchThrow.java)
  at CatchThrow.dot(Native Method)
  at CatchThrow.main(CatchThrow.java)
  In Java:
  java.lang.IllegalArgumentException: thrown from C code
  这个回调方法抛出一个"NullPointerExcetption"。当"CallVoidMethod"返回控制给本地方法时,本地代码将通过"JNI"函数"ExceptionOccurred"侦测到这个异常.在我们例子中,当一个异常被侦测到,本地代码输出一个通过调用"ExceptionDescribe"的异常的详细信息,使用"ExceptionClear"来清除异常,同时抛出一个"IllegalArgumentException"来替代。
  通过"JNI"(例如,通过调用"ThrowNew")产生的一个未觉的异常,不会立即中断本地方法的执行。这和怎样处理在"Java"编程语言中的异常行为(exception behave)不一样。当一个异常在"Java"中被抛出时,虚拟器自动地改变控制流程到最近套装的"try/catch"声明来匹配异常类型。然后虚拟器清除未决异常和执行异常处理。对照相反(In contrast),"JNI"编程者必须清晰地实现控制流程,在一个异常已经发生后。
  6.1.2 一个工具函数(A Utility Function)
  抛出一个包含首次发现的异常类的异常,然后调用了"ThrowNew"函数。为了简单化这个任务,我们能写一个工具函数来抛出这个命名的异常:
  void JNU_ThrowByName(JNIEnv *env), const char *name, const char *msg)
  {
  jclass cls = (*env)->FindClass(env, name) ;
  if (cls != NULL){
  (*env)->ThrowNew(env, cls, msg) ;
  }
  (*env)->DeleteLocalRef(env, cls) ;
  }
  在这本书中,"JNU"前缀代表"JNI Utilities"("JNI"工具)。"JNU_ThrowByName"首席使用"FindClass"函数发现异常类型。如果"FindClass"失败(返回NULL),虚拟器必须抛出一个异常(例如"NoClassDefFoundError")。在这情况中,"JNU_ThrowByName"没有尝试抛出另一个异常。如果"FindClass"成功,我们抛出调用"ThrowNew"产生的命名异常。当"JNU_ThrowByName"放回,保证有个未决的异常,虽然这个未决异常不一定是名字参数指定的异常。我们保证了删除在这个函数创建的异常类型的局部引用。传地NULL给"DeleteLocalRef"是一个空操作,如果"FindClass"失败返回一个NULL,这是一个正确的行为。
  6.2 恰当地异常处理
  "JNI"编程者必须必须预知(foresee)可能的异常情况,同时写代码来检查和处理这些情况。适当地异常处理有时冗长乏味(tedious),但为了产生强壮的应用程序,它是必须的。
  6.2.1 检查异常(Checking for Exceptions)
  有两种方法检查是否有错误发生。
  1.大多"JNI"函数使用一个清晰的(distinct)返回值(例如NULL)来指示一个错误发生。错误返回值也暗示(implies)在当前线程有个未解决的异常。(在返回值中编码错误条件是在C语言中常见的做法)
  下面的例子说明使用NULL值,做为在错误检查中"GetFieldID"的返回值。这例子包含两个部分:一个类窗口(class Window)定义大量实例成员域(handle,length and width)和一个本地方法缓冲这些域的域ID。即使这些域存在于"Window"类中,我们任然需要检查可能从"GetFileID"返回的错误,因为虚拟器可能不能分配需要描述一个域ID(represent a field ID)的内存。
  public class Window{
  long handle ;
  int length ;
  int width ;
  static native void initIDs() ;
  static {
  initIDs() ;
  }
  }
  jfieldID FID_Window_handle ;
  jfieldID FID_Window_length ;
  jfieldID FID_Window_width ;
  JNIEXPORT void JNICALL
  Java_Window_initIDs(JNIEnv *env, jclass classWindow)
  {
  FID_Window_handle =
  (*env)->GetFieldID(env, classWindow, "handle","J") ;
  if( FID_Window_handle == NULL ){
  return ;
  }
  FID_Window_length =
  (*env)->GetFieldID(env, classWindow, "length","I") ;
  if( FID_Window_handle == NULL ){
  return ;
  }
  FID_Window_width =
  (*env)->GetFieldID(env, classWindow, "width","I") ;
  }
  2.当使用一个"JNI"函数的返回值不能标记一个错误发生时,本地代码必须依赖产生异常来做错误检查。在当前线程中执行一个未决异常检测的"JNI"函数是"ExceptionOccurred"。("ExceptionCheck"被添加到"Java 2 SDK release 1.2"中。)例如,"JNI"函数"CallIntMethod"不能编码错误情况在返回值中。错误情况返回值的典型选择(Typical choices),例如"NULL"和"-1",不能工作,因为他们可能是被调方法返回的合法值。思考一个"Fraction"类,这个类的"floor"f方法返回"fraction"的值的整数部分,同时一些本地代码调用了这个方法。
  public class Fraction{
  // details such as constructors omitted
  int over , under ;
  public int  floor{
  return Math.floor((double)over/under) ;
  }
  }
  void f(JNIEnv *env, jobject fraction)
  {
  jint floor = (*env)->CallIntMethoed(env, fraction, MID_Fraction_floor) ;
  if( (*env)->ExceptionCheck(env)){
  return ;
  }
  ...
  }
  当"JNI"函数返回一个清晰的错误代码时,本地代码任然可以明显地通过调用,例如"ExceptionCheck",来检查异常。然而作为替代,它是更有效的检查明确的错误返回值。如果一个"JNI"函数返回它的错误值,在当前线程中的一个后续的"ExcptionCheck"调用保证返回"JNI_TRUE"。
  6.2.2 处理异常(Handign Exceptions)
  通过两种方式,本地代码可以处理一个未决的异常:
  .本地方法实现能选择立即返回,引起异常在调用者中处理它。
  .本地代码通过调用"ExceptionClear"能清理异常,然后执行它自己的异常处理代码。
  在调用任何后续的JNI函数前,检查,处理和清楚一个未决的异常是非常重要(extremely important)。调用带有一个未决异常的大多数"JNI"函数(带有一个你没有清楚地清理异常)可能导致不期望的结果。当在当前线程中有一个未决的异常时,你能安全调用的JNI函数很少。11.8.2部分详细说明(specify)这些"JNI"函数的完整的清单。一般说,当这儿有一个未决的异常,你能调用被设计来处理异常的"JNI"函数,同时调用"JNI"函数来释放各种通过"JNI"暴露的虚拟器资源。
  当异常发生的时候,释放资源经常是必须的。在下面的例子中,本地方法首先通过"GetStringChars"调用来获得字符串的内容。如果一个后续(subsequent)操作失败,需调用"ReleaseStringChars":
  JNIEXPORT void JNICALL
  Java_pkg_Cls_f(JNIEnv *env, jclass cls, jstring jstr)
  {
  const jchar *cstr = (*env)->GetStringChars(env, jstr) ;
  if ( c_str == NULL ){
  return  ;
  }
  ....
  if( ...){
  (*env)->ReleaseStringChars(env, jstr, Cstr) ;
  return ;
  }
  ...
  (*env)->ReleaseStringChars(env, jstr, cstr) ;
  }
  当有个未决的异常,"ReleaseStringChars"的第一次被调用。本地方法的实现释放字符资源和事后立即返回没有首先清理异常。
  6.2.3 在工具函数中的异常
  写工具函数的编程者应该是花费特别的注意力在确保异常传播(propage)给本地方法的调用者.特别是(In particular),我们强调下面两个问题(issue):
  .更合适地,工具函数应该提供一个特别的返回值来指示一个异常发生。者简化了调用者检查未决异常的任务。
  .另外(In addition),工具函数在异常处理代码中应该按照规则(5.3部分)来管理局部引用。
  为了说明,让我们介绍个工具函数,它是执行一个基于一个实例方法的名字和描述的回调。
  jvalue
  JNU_CallMethodByName(JNIEnv *env,
  jboolean *hasException,
  jobject obj,
  const char *name,
  const char *descriptor,....)
  {
  va_list args;
  jclass clazz ;
  jmethodID mid ;
  jvalue result;
  if ( (*env)->EnsureLocalCapacity(env, 2) == JNI_OK) {
  clazz = (*env)->GetObjectClass(env, obj) ;
  mid = (*env)->GetMethodID(env, clazz, name, descriptor) ;
  if( mid ){
  const char *p = descriptor ;
  while( *p != ')') p++ ;
  / * skip ')' */
  p++ ;
  va_start(args, descriptor) ;
  switch(*p) {
  case 'V'
  (*env)->CallVoidMethod(env, obj, mid, args) ;
  break ;
  case '[':
  case 'L':
  result.l = (*env)->CallObjectMethodV(
  env, obj, mid, args) ;
  break ;
  case 'Z':
  result.z = (*env)->CallBooleanMethodV(
  env, obj, mid, args) ;
  break ;
  case 'B':
  result.b = (*env)->CallByteMethodV(
  env, obj, mid, args) ;
  break ;
  case 'C':
  result.b = (*env)->CallCharMethodV(
  env, obj, mid, args) ;
  break ;
  case 'S':
  result.s = (*env)->CallShortMethodV(
  env, obj, mid, args) ;
  break ;
  case 'I':
  result.i = (*env)->CallIntMethodV(
  env, obj, mid, args) ;
  break ;
  case 'J':
  result.j = (*env)->CallLongMethodV(
  env, obj, mid, args) ;
  break ;
  case 'F':
  result.f = (*env)->CallFloatMethodV(
  env, obj, mid, args) ;
  break ;
  case 'D':
  result.d = (*env)->CallDoubleMethodV(
  env, obj, mid, args) ;
  break ;
  default:
  (*env)->FatalError(env, "illegal descriptor") ;
  break ;
  }
  va_end(args) ;
  }
  (*env)->DeleteLocalRef(env, clazz) ;
  }
  if ( hasException){
  *hasException = (*env)->ExceptionCheck(env) ;
  }
  return result ;
  }
  在其他参数中,"JNU_CallMethodByName"有个指向一个"jboolean"的指针。如果所有事都成功,这个"jboolean"被设置为"JNI_FALSE"。如果一个异常在这个函数的执行期间的任何位置发生,这个"jboolean"被设置为"JNI_TRUE"。这给"JNU_CallMethodByName"的调用者一个容易的方法来检测可能的异常。
  "JNU_CallMethodByName"首先确信它能创建两个局部引用:一个类的引用和另一个为了从方法调用返回的结果。下一部,从"object"(对象)得到类的引用和查询方法ID。依靠返回类型,"switch"声明分发到对应的"JNI"方法调用函数。在回调返回后,如果"hasException"不是NULL,我们调用ExceptionCheck来检查未决异常。
  "ExceptionCheck"函数是新加的在"Java 2 SDK release 1.2"中。相似的函数是"ExceptionOccurred"函数。不同的是"ExceptionCheck"没有返回异常对象的引用,但当有个未决的异常时返回"JNI_TRUE"和当没有未决异常时返回"JNI_FALSE"。当本地代码只需要知道是否有一个异常,但不需要得到这个异常对象的引用时,"ExceptionCheck"简化了局部引用管理。在"JDK release 1.1"中,前面的代码必须被写,如下:
  if(hasException){
  jthrowable exc = (*env)->ExceptionOccurred(env) ;
  *hasException = exc != NULL;
  (*env)->DeleteLocalRef(env, exc) ;
  }
  这额外的"DeleteLocalRef"调用是必须的,为了删除对异常对象的几部引用。
  使用"JNU_CallMethodByName"函数,我们能重写"InstanceMethodCall.nativeMethod"的实现,在4.2部分,如下(as follows):
  JNIEXPORT void JNICALL
  Java_InstanceMehtodCall_nativeMethod(JNIEnv *env, jobject obj)
  {
  printf("In C\n") ;
  JNU_CallMethodByName(env, NULL, obj, "callback","()V") ;
  }
  在"JNU_CallMethodByName"调用后,我们不需要要检查异常,因为本地方法后来立即返回(return immediately afterwards)。
  Java本地接口(JNI)编程指南和规范(第七章)第七章调用接口
  这章告诉你怎样能嵌入一个"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
  #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 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 -L -lthread -ljava invoke.c
  "-lthread"选项表明我们使用Java虚拟器实现带有本地线程支持(8.1.5部分)。"-ljava"选项指明"libjava.so"是"Solaris"共享库,这共享库实现了"Java"虚拟器。
  在Win32上带有的"Microsoft Visual C++"编译器,命令行来编译和链接同样代码和"JDK 1.1 release":
  cl -I -MD invoke.c -link \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
  #include
  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 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 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)顺序将可能不同,依赖于在线程安排中的随机因素。