1. 程式人生 > >Java本地介面(JNI)程式設計指南和規範(第七章)

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 <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"函式成功返回時,當前本地執行緒已經引導自己進入"Java"虛擬器。在這方面,就象執行一個本地方法。因此,在其它事中,能做出"JNI"呼叫來呼叫"Prog.main"方法。

最終,程式呼叫"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"。總得來說,不同的供應商可以命名他們的不同的虛擬器實現。

一旦編譯和連結完成,你能從行命令執行可執行的檔案。你可能得到一個系統沒有發現一個共享庫或一個動態連結庫的錯誤。在"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"釋出。

解決方法是使用執行時動態連結到載入的指定的應用程式需要的虛擬器庫。然後,虛擬器庫的名字能被使用一種應用程式指定的方法來配置。例如, 下面的"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"請求。我們想嵌入一個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)順序將可能不同,依賴於線上程安排中的隨機因素。