摘要
Jace是一種免費的開放源代碼的工具,它使我們能夠輕松地開發JNI(Java本機接口)代碼。本篇文章詳細地分析了JNI API的問題,以及如何使用Jace解決這些問題。
如果沒有更深的了解,我們一定會以為Sun設計JNI的目的是為了不讓Java編程人員使用它。畢竟,類型安全形同虛設,缺乏錯誤檢查機制,進行一次簡單的Java方法調用需要4次或更多的JNI調用,這都是JNI明顯的不足之處。另外,我們還必須管理JNIEnv指針,不能在多個線程中使用JNI調用,必須為每種可能的操作在9個函數調用中進行選擇,而且異常信息的獲取也非常地困難。這還只是JNI所出現問題的一部分,我們還能發現許多其他問題。
這些限制中的許多部份都與JNI與C語言的綁定有關,C語言本身對類型安全、異常處理機制的支持也非常不好。盡管目前大多數的編程人員都已經能夠使用C++編寫代碼,但Sun沒有放棄C編程人員,這也是JNI目前這種狀況的原因。不幸的是,這種很難使用的API給開發人員帶來了許多困難。
Jace是一款免費的開放源代碼的工具包,旨在使JNI編程變得更加簡單。它支持由Java類文件自動生成C++代理類以及C++與Java的異常、數組、包、對象的整合,管理Java引用的線程綁定和生命周期。更為重要的是,它能夠使我們開發更小、更易於理解、在編譯時類型安全的模塊。
JNI的類型系統
Jace最基本的特點是它使用C++代理類來表達Java類型。為了真正地理解代理類的優點,我們首先需要來看看JNI的類型系統。Sun在JNI中使用了24種C類型來表示所有可能的Java類型。JNI包含有9個簡單類型:
·jboolean
·jbyte
·jchar
·jshort
·jint
·jlong
·jdouble
·jfloat
·void
JNI有14種引用類型,如下圖所示:
(圖:picture01)
另外,JNI有一個復合型的類型jvalue,它能夠表達所有的簡單和引用類型。
Jace類型系統
圖2表示基本的Jace數據類型的類圖表。這些類是我們訪問Jace運行時間庫的簡單的接口,它與JNI的數據類型對應非常緊密。
(圖:picture02)
Jace的數據類型系統是直接以24種JNI數據類型為基礎的,對於每一種JNI數據類型而言,Jace都有一個相應的C++代理類。9種JNI簡單數據類型以及jvalue、jclass、jobject、jstring和jthrowable都直接映射為相應的Jace代理類,JNI的jarray數據類型以及9個派生的數組數據類型都被映射為一種基於模板的JArray數據類型。在下面的部分中,我們將對每種C++代理類進行詳細的解釋。
簡單類
9個簡單的類可以作為9種JNI的簡單數據類型的封裝器。我們可以將這些類作為參數,並返回其他C++代理類的值:
/* 獲得值為“A String”的java.lang.String的哈希碼值
*/
JInt hashCode = String( "A String" ).hashCode();
我們也可以將這些類作為JArray類的模板參數:
/* 創建一個大小為512的字節緩沖區
*/
JArray<JByte> buffer( 512 );
JValue
JValue是所有代理類的基礎類,它能夠表達Java所有的簡單和引用數據類型。每個JValue有一個JClass,該JClass表示jvalue相應的jclass。我們只能提供一個JNI的jvalue數據類型構建JValue,JValue就成為了jvalue的持有者。大多數開發人員無需與JValues直接打交道。
JClass
JClass表示JNI的數據類型jclass,它提供了訪問其jclass和在不同的JNI調用中表示jclass的字符串(例如,java/lang/Object和Ljava/lang/Object)。Jace的框架使用JClass實例提供進行GetMethodID()、GetFieldID()和NewObjectArray()等JNI調用所必需的信息。大多數開發人員無需直接與JClass打交道。
JObject
JObject類表示JNI的數據類型jobject,並作為所有引用數據類型的基礎類。除了最重要的JValue::getJavaValue()外,JObject類還提供了getJavaObject()方法。除了getJavaObject()能夠解開jvalue,並將它放在jobject中外,這二個方法的功能相當。
JObject比較有趣,因為Java的引用類型有一些Java的簡單數據類型所不具備的特性:
·引用類型沒有自己的值,它們只是指向這些值。我們可以用二種方式構建JObject子類。第一種方式,我們可以將它構建為現有Java對象的引用。通過使用接受jobject(或包含jobject的jvalue)為參數的構建器或者使用C++的拷貝構建器,我們就可以以這種方式構建JObject子類。
在對JObject子類實例化時,子類實例將它自己提交給作為參數提供的jobject引用。(實例使用NewGlobalRef()創建jobject的全局性引用。)
using jace::java::net::URL;
JNIEXPORT void JNICALL Java_foo_Bar_someMethod( JNIEnv *env, jobject jURL ) {
/* 創建jURL的一個引用,而不是一個新的URL
*/
URL url( jurl );
/* 既然已經實例化了C++代理對象,我們就就可以方便地對jURL調用toString()等方法
*/
std::string urlString = url.toString();
}
第二種方法,我們可以通過創建一個新的Java對象來構建JObject子類。可以通過調用其他子類的構建器創建新的對象,子類可以使用JNI調用合適的Java構建器。在構建Java對象後,子類就會創建一個新的指向它的全局性引用:
/* 在foo.txt上創建一個新的FileOutputStream。
*/
jace::java::io::FileOutputStream output( "foo.txt" );
無論如何創建JObject子類,子類唯一的操作是對在構建時創建的全局性引用調用DeleteGlobalRef()。
·引用類型的數據可能是空值,通過調用JObject::isNull(),我們可以檢測C++代理類是否指向一個為空值的Java對象:
JNIEXPORT void JNICALL Java_foo_Bar_someMethod( JNIEnv *env, jstring
javaString ) {
String str( javaString );
if ( str.isNull() ) {
cout << "Error - The argument, javaString, must not be
null." << endl;
}
}
Throwable和String
Throwable和String C++代理類都是由JObject派生的(與所有的引用類型的代理類一樣),它們(還有其他一些類)是Jace庫的核心部份,向用戶提供C++和Java之間更緊密的整合能力。
Jace的功能
Jace可以提供許多功能,其中包括線程管理、異常管理、自動類型轉換和其他一些功能。下面我們來討論這些功能:
線程管理
在JNI中存在著一些線程方面的問題:
·JNIEnv指針只能在獲得它們的線程上使用。
·大多數的JNI數據類型只能在它們存在的線程上使用。
·在調用JNI函數之前,C++線程必須連接到JVM上
Jace解決了這些問題。第一,Jace庫中的每個函數自動地獲得一個只能在當前線程上使用的JNIEnv指針。第二,Jace創建必要的JNI類型的全局性引用。例如,JClass創建其jclass成員的全局性引用,JObject創建其jobject成員的全局性引用。與只能供當前線程使用的局部性引用不同的是,全局性變量能夠供所有線程使用。
最後,Jace中的每個函數能夠確保在調用JNI函數之前,當前的線程能夠連接到JVM上。
異常管理
異常處理是JNI編程的一個短肋。在異常處理方面,Jace有二條方針:
1)Jace檢查它執行的每個JNI函數的返回碼。如果有錯誤發生,Jace清除JNI異常,然後發出Jace的JNIException消息。
2)如果由於方法發出異常消息,Jace發現Java方法的調用失敗,Jace則檢查並清除JNI異常,然後創建該異常的一個C++代理實例,並發出C++代理。
using namespace jace::java::net;
void readGoogle() {
try {
/* 當Jace在內部執行NewObject時,它會檢查是否有異常發生,
* 如果JNI函數ExceptionOccurred返回一個異常,則Jace清除
* 該異常,創建一個相應的C++代理,並發出它。
*/
URL url( "http://www.google.com" );
}
/* 在這裡,我們可以獲得Jace發出的C++代理異常
*/
catch ( MalformedURLException& e ) {
cout << e;
}
}
自動類型轉換
Jace提供C++和Java簡單數據類型之間的自動數據類型轉換。我們可以在C++代理需要java::lang::String的地方使用C++的std::string或char*,我們也可以在C++代理方法需要JBoolean、JInt和JChar簡單JNI數據類型的地方使用bool、int和char等C++數據類型:
using jace::javax::swing::JFrame;
JFrame createFrame( const std::string& title, int x, int y ) {
/* JFrame的原型是JFrame( java::lang::String str );,
* Jace自動地在std::string和java::lang::String之間進行轉換。
*/
JFrame frame( title );
/* setLocation的原型是setLocation( JInt x, JInt y );,
* Jace自動地在int之間JInt進行轉換。
*/
frame.setLocation( x , y );
return frame;
}
C++集成
Jace包括C++代理生成工具━━BatchGen,Jace開發人員對Java運行時間庫環境(JRE)的rt.jar使用BatchGen生成C++代理類。Jace開發人員已經對這些生成的代理類進行修改,以更好地與C++語言和標准庫進行集成。
例如,java.lang.Object有一個附加的操作符<<(ostream& out, Object& object),java.lang.String也有一些包括+()、=()和==()在內的附加方法,可以使它與std::strings和char*s更好地進行集成。
類型安全字段和方法訪問
C++的代理生成是Java對象類型安全訪問的基礎。對於給定的Java類文件,Jace能夠生成完全相同的方法和字段的C++代理類,我們可以以與調用Java中類似方法相同的方式調用C++代理方法,字段是通過同名的方法進行訪問的:
/* 一個Java類
*/
public class Foo {
public int aField;
public String aMethod( URL aURL );
}
/* 從C++中訪問C++代理
*/
Foo foo;
foo.aField() = 14;
String result = foo.aMethod( URL( "http://www.google.com" ) );
Jace提供了二種工具━━ProxyGen和BatchGen,我們可以用這二種工具從Java類文件中生成C++代理類。
類型安全數組
我們可以使用Jace的模板JArray類訪問Java的數據類型安全數組。根據數組的數據類型,Jace調用合適的Get<Type>ArrayElement()和Set<Type>ArrayElement() JNI函數:
JArray<JInt> intArray( 10 ); // 導致對NewIntArray的調用
int i = intArray[ 2 ]; // 導致對GetIntArrayElements和應用
JArray<String> stringArray( 5 ); // 導致對NewObjectArray的調用
std::string str = stringArray[ 2 ]; // 導致對GetObjectArrayElement的調用
Jace工具
ProxyGen和BatchGen可以用來生成C++代理類。ProxyGen用來處理一個類文件,BatchGen則用來處理一個jar文件中所有的類。
ProxyGen
ProxyGen能夠將一個Java類文件的頭部文件或源文件轉出到標准輸出。ProxyGen總是會包含生成的C++代理類中的public方法和字段,根據指定的訪問水平,它也會包含protected、package或private方法和字段。
用法:ProxyGenerator <類文件> <頭部 | 源文件> [ 選項 ]
選項可能是:
-protected :生成protected字段和成員
-package :生成package字段和成員
-private :生成private字段和成員
BatchGen
在生成C++代理類的頭文件和源代碼文件方面,BatchGen與ProxyGen非常相似。二者的不同之處是,ProxyGen只處理一個Java類文件,BatchGen則處理由多個Java類文件組成的jar或zip文件。另外,ProxyGen將頭文件和源代碼文件輸出到標准輸出,BatchGen則將頭文件和源代碼文件輸出到指定的目錄。
用法:BatchGenerate <包含Java類的jar或zip>
<頭文件的目標目錄>
<源代碼文件的目標目錄>
[ 選項 ]
選項可能是:
-protected :生成protected的字段和成員
-package :生成package字段和成員
-private :生成private字段和成員
Jace還會有哪些改進
將來,Jace的性能會進一步地提高,對數組提供更好的支持,當然了,在其他一些方面也會有所改進。例如,Jace將把Java的數組作為標准的C++容器,並兼容for_each()等函數。另外,它還會支持數組元素的後台緩沖和預先取等功能。
結論
JIN存在的許多問題都與它和C的綁定有關,通過將JNI與C++綁定,Jace很好地解決了JNI存在的問題,將有助於Java的普及。