查看文章 |
在本篇中,将会涉及关于JNI编程更深入的话题,包括:在native方法中访问Java类的域和方法,将Java中自定义的类作为参数和返回值传递等等。了解这些内容,将会对JNI编程有更深入的理解,写出的程序也更清晰,易用性更好。 1. 在一般的Java类中定义native方法 在前两篇的例子中,都是将native方法放在main方法的Java类中,实际上,完全可以在任何类中定义native方法。这样,对于外部来说,这个类和其他的Java类没有任何区别。 2. 访问Java类的域和方法 native方法虽然是native的,但毕竟是方法,那么就应该同其他方法一样,能够访问类的私有域和方法。实际上,JNI的确可以做到这一点,我们通过几个例子来说明, public class ClassA { String str_ = "abcde"; int number_; public native void nativeMethod(); private void javaMethod() { System.out.println("call java method succeeded"); } static { System.loadLibrary("ClassA"); } } 在这个例子中,我们在一个没有main方法的Java类中定义了native方法。我们将演示如何在nativeMethod()中访问域str_,number_和方法javaMethod(),nativeMethod()的C++实现如下, JNIEXPORT void JNICALL Java_testclass_ClassCallDLL_nativeMethod(JNIEnv *env, jobject obj) { // access field jclass cls = env->GetObjectClass(obj); jfieldID fid = env->GetFieldID(cls, "str_", "Ljava/lang/String;"); jstring jstr = (jstring)env->GetObjectField(obj, fid); const char *str = env->GetStringUTFChars(jstr, false); if(std::string(str) == "abcde") std::cout << "access field succeeded" << std::endl;
jint i = 2468; fid = env->GetFieldID(cls, "number_", "I"); env->SetIntField(obj, fid, i);
// access method jmethodID mid = env->GetMethodID(cls, "javaMethod", "()V"); env->CallVoidMethod(obj, mid); } 上面的代码中,通过如下两行代码获得str_的值, 第一行代码获得str_的id,在GetFieldID函数的调用中需要指定str_的类型,第二行代码通过str_的id获得它的值,当然我们读到的是一个jstring类型,不能直接显示,需要转化为char*类型。 接下来我们看如何给Java类的域赋值,看下面两行代码, 第一行代码同前面一样,获得number_的id,第二行我们通过SetIntField函数将i的值赋给number_,其他类似的函数可以参考JDK的文档。 访问javaMethod()的过程同访问域类似, 需要强调的是,在GetMethodID中,我们需要指定javaMethod方法的类型,域的类型很容易理解,方法的类型如何定义呢,在上面的例子中,我们用的是()V,V表示返回值为空,()表示参数为空。如果是更复杂的函数类型如何表示?看一个例子, 3. 在native方法中使用用户定义的类 JNI不仅能使用Java的基础类型,还能使用用户定义的类,这样灵活性就大多了。大体上使用自定义的类和使用Java的基础类(比如String)没有太大的区别,关键的一点是,如果要使用自定义类,首先要能访问类的构造函数,看下面这一段代码,我们在native方法中使用了自定义的Java类ClassB, jclass cls = env->FindClass("Ltestclass/ClassB;"); jmethodID id = env->GetMethodID(cls, "<init>", "(D)V");
jdouble dd = 0.033; jvalue args[1]; args[0].d = dd; jobject obj = env->NewObjectA(cls, id, args); 首先要创建一个自定义类的引用,通过FindClass函数来完成,参数同前面介绍的创建String对象的引用类似,只不过类名称变成自定义类的名称。然后通过GetMethodID函数获得这个类的构造函数,注意这里方法的名称是"<init>",它表示这是一个构造函数。 jobject obj = env->NewObjectA(cls, id, args); 通过以上介绍的三部分内容,native方法已经看起来完全像Java自己的方法了,至少主要功能上齐备了,只是实现上稍麻烦。而了解了这些,JNI编程的水平也更上一层楼。下面要讨论的话题也是一个重要内容,至少如果没有它,我们的程序只能停留在演示阶段,不具有实用价值。 4. 异常处理 在C++和Java的编程中,异常处理都是一个重要的内容。但是在JNI中,麻烦就来了,native方法是通过C++实现的,如果在native方法中发生了异常,如何传导到Java呢? JNI提供了实现这种功能的机制。我们可以通过下面这段代码抛出一个Java可以接收的异常, jclass errCls; env->ExceptionDescribe(); env->ExceptionClear(); errCls = env->FindClass("java/lang/IllegalArgumentException"); env->ThrowNew(errCls, "thrown from C++ code"); 如果要抛出其他类型的异常,替换掉FindClass的参数即可。这样,在Java中就可以接收到native方法中抛出的异常。 至此,JNI编程系列的内容就完全结束了,这些内容都是本人的原创,通过查阅文档和网上的各种文章总结出来的,相信除了JDK的文档外,没有比这更全面的讲述JNI编程的文章了。当然,限于篇幅,有些地方不可能讲的很细。限于水平,也可能有一些错误。文中所用的代码,都亲自编译执行过。希望这些内容能为需要的朋友提供帮助,毕竟,分享是一种美德。
|

