分享

用Jace整合Java和C++

 9loong 2012-08-08
2007-01-01 23:26:30  来源:yesky  作者:刘彦青编译  编辑:刘彦青编译  

  摘要

  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的普及。

==============================================================================

项目网址:http://code.google.com/p/jace/


(###)

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多