本文目录一览:
怎样使用javap来反编译class文件
当然可以了。在java软件包的bin文件夹下有一个javap程序,就是用来反编译的。 用法如java一样。就是先用javac变成字节码文件就是.class文件后,用javap 加上文件名就可以反编译了。 举个例子test.class,反编译用javap test就行了。
eclipse bytecode visualizer怎么用
1、使用javap命令阅读
进入Test.class目录,然后使用javap -v Test.class out将字节码文件读取后存入out文件,然后用notepad++打开out文件:
test()函数的样子:
从字节码可以看到,编译器已经自动的将变量a的值合并在一起,成为了xy1,因此相当于xy1两个字符串的比较,两者都处于常量区,因此相等;
2、使用eclipse的Bytecode visualizer插件阅读
安装好插件之后,首先将其配置一下,以显示最大化的字节码信息
然后打开eclipse的Navigator窗口,找到编译好的Test.class,右键,用字节码方式打开,就会看到更易读的字节码
同时在编辑器的右侧,能够自动查看该段代码的流程图,非常方便;
使用这两种方式,就能够查看代码编译后的内幕,对一些奇怪的语法现象,就能够自己找出为什么了
但是在测试的过程中,自己写了个print(ListString strs)的方法,使用Javap命令和Byte visualizer两者,输出的字节码中,前者带着泛型信息,后者进行了泛型擦除,两者不同
附上出处链接:
java反编译后是scala吗
javap是JDK提供的一个反编译工具。常用的选项有-c -l -s。如果仅仅是想查看编译的得到的字节码文件中函数与变量,不需要带选项。使用javap很容易发现源文件与编译后得到的字节码文件有什么不同,可以加深对编译器的理解。
javap -help
Usage: javap options classes...
where options include:
-c Disassemble the code
-classpath pathlist Specify where to find user class files
-extdirs dirs Override location of installed extensions
-help Print this usage message
-Jflag Pass flag directly to the runtime system
-l Print line number and local variable tables
-public Show only public classes and members
-protected Show protected/public classes and members
-package Show package/protected/public classes and members (default)
-private Show all classes and members
-s Print internal type signatures
-bootclasspath pathlist Override location of class files loaded by the bootstrap class loader
-verbose Print stack size, number of locals and args for methods If verifying, print reasons for failure
Scala是基于JVM的,所有其字节码和Java编译得到的字节码应该是一致的。首先从Hello World开始
object Main {
def main(args: Array[String]) = {
println("Hello, " + args(0))
}
}
使用scalac编译都得到两个字节码文件:Main.class和Main$.class。
在class文件所在的目录分别运行javap Main和javap Main$得到如下结果:
Compiled from "Main.scala"
public final class Main extends java.lang.Object{
public static final void main(java.lang.String[]);
}
Compiled from "Main.scala"
public final class Main$ extends java.lang.Object implements scala.ScalaObject{
public static final Main$ MODULE$;
public static {};
public void main(java.lang.String[]);
}
Scala的object是单例模式。上面的反编译结果给了我们Scala实现原理的提示。MODULE$指向类的实例,相当于this。而方法也是被声明为静态的。做了一个测试,更加直观。说如果不是去发射火箭,Object的代码跟Java的静态代码可以认为是等价的。
把上面的代码稍作修改:
case class Main {
def main(args: Array[String]) = {
println("Hello, " + args(0))
}
}
在class文件所在的目录分别运行javap Main和javap Main$得到如下结果:
Compiled from "Main.scala"
public class Main extends java.lang.Object implements scala.ScalaObject,scala.Product,scala.Serializable{
public scala.collection.Iterator productIterator();
public scala.collection.Iterator productElements();
public void main(java.lang.String[]);
public int hashCode();
public java.lang.String toString();
public boolean equals(java.lang.Object);
public java.lang.String productPrefix();
public int productArity();
public java.lang.Object productElement(int);
public boolean canEqual(java.lang.Object);
public Main();
}
Compiled from "Main.scala"
public final class Main$ extends scala.runtime.AbstractFunction0 implements scala.ScalaObject,scala.Serializable{
public static final Main$ MODULE$;
public static {};
public final java.lang.String toString();
public boolean unapply(Main);
public Main apply();
public java.lang.Object readResolve();
public java.lang.Object apply();
}
与输入的文件相比,Scala添加了许多东西:
1. Scala自动帮助Case类生成toString, equals,hashCode等方法。
2. Scala自动帮助Case类生成apply方法,不需要new就可以直接创建类的实例或引用。
3. 对于类的成员,Scala自动生成成员的Getter和Setter。
4. Case类提供对模式匹配的支持。
下面来看Scala对类和隐式转换的处理,有一个Rational类:
class Rational(n: Int, d: Int) {
require(d != 0)
private val g = gcd(n.abs, d.abs)
val numer = n / g
val denom = d / g
def this(n: Int) = this(n, 1)
def +(that: Rational): Rational =
new Rational(numer * that.denom + that.numer * denom, denom * that.denom)
def +(i: Int): Rational = new Rational(numer + i * denom, denom)
def -(that: Rational): Rational =
new Rational(numer * that.denom - that.numer * denom, denom * that.denom)
def -(i: Int): Rational = new Rational(numer - i * denom, denom)
def *(that: Rational): Rational = new Rational(numer * that.numer, denom * that.denom)
def *(i: Int): Rational = new Rational(numer * i, denom)
def /(that: Rational): Rational = new Rational(numer * that.denom, denom * that.numer)
def /(i: Int): Rational = new Rational(numer, denom * i)
override def toString = numer + "/" + denom
private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)
}
object Rational{
implicit def intToRational(x: Int) = new Rational(x)
}
javap得到:
Compiled from "Rational.scala"
public class Rational extends java.lang.Object implements scala.ScalaObject{
public static final Rational intToRational(int);
public int numer();
public int denom();
public Rational(int);
public Rational $plus(Rational);
public Rational $plus(int);
public Rational $minus(Rational);
public Rational $minus(int);
public Rational $times(Rational);
public Rational $times(int);
public Rational $div(Rational);
public Rational $div(int);
public java.lang.String toString();
public Rational(int, int);
}
Compiled from "Rational.scala"
public final class Rational$ extends java.lang.Object implements scala.ScalaObje
ct{
public static final Rational$ MODULE$;
public static {};
public Rational intToRational(int);
}
对于普通类,Scala自动添加的东西稍微少一些。一直出现的还有一个特别的函数:public static {};通过带参数-c的javap可以看到其汇编代码:
public static {};
Code:
0: new #9; //class Rational$
3: invokespecial #12; //Method "init":()V
6: return
new创建一个对象并将其引用值压入栈顶,invokespecial调用父类的构造方法,return从当前方法返回void。该方法实际上就是一个创建自身的静态方法。Java虚拟机指令参考
Rational$中的intToRational是我们提供的隐式类型转换,将一个Int转换为Rational。其汇编代码如下:
public Rational intToRational(int);
Code:
0: new #16; //class Rational
3: dup
4: iload_1
5: invokespecial #20; //Method Rational."init":(I)V
8: areturn
new首先创建一个Rational对象并将其引用值压入栈顶,dup复制栈顶数值并将复制值压入栈顶,iload_1将第二个int型本地变量推送至栈顶,invokespecial调用Rational的构造方法,最后areturn从当前方法返回对象引用。
我们用如下代码来测试隐式类型转换:
import Rational._
object RationalTest {
def main(args: Array[String]) {
val r = new Rational(2,3)
println(2 * r)
}
}
2 * r本身不合法的,因为Int不存在*(Rational)的方法,由于隐式转换的存在,Scala将做一些转换工作。上面程序的汇编代码如下:
Compiled from "RationalTest.scala"
public final class RationalTest$ extends java.lang.Object implements scala.Scala
Object{
public static final RationalTest$ MODULE$;
public static {};
Code:
0: new #9; //class RationalTest$
3: invokespecial #12; //Method "init":()V
6: return
public void main(java.lang.String[]);
Code:
0: new #16; //class Rational
3: dup
4: iconst_2
5: iconst_3
6: invokespecial #20; //Method Rational."init":(II)V
9: astore_2
10: getstatic #25; //Field Rational$.MODULE$:LRational$;
13: iconst_2
14: invokevirtual #29; //Method Rational$.intToRational:(I)LRational;
17: aload_2
18: invokevirtual #33; //Method Rational.$times:(LRational;)LRational;
21: astore_3
22: getstatic #38; //Field scala/Console$.MODULE$:Lscala/Console$;
25: aload_3
26: invokevirtual #42; //Method scala/Console$.println:(Ljava/lang/Object;
)V
29: return
}
在做乘法($times)之前调用了 intToRational,返回一个Rational对象, 调用Rational对象的*方法已经合法化了。
如何在C/C++中调用Java
如何在C/C++中调用Java
java跨平台的特性使Java越来越受开发人员的欢迎,但也往往会听到不少的抱怨:用Java开发的图形用户窗口界面每次在启动的时候都会跳出一个控制台窗口,这个控制台窗口让本来非常棒的界面失色不少。怎么能够让通过Java开发的GUI程序不弹出Java的控制台窗口呢?其实现在很多流行的开发环境例如JBuilder、Eclipse都是使用纯Java开发的集成环境。这些集成环境启动的时候并不会打开一个命令窗口,因为它使用了JNI(Java Native Interface)的技术。通过这种技术,开发人员不一定要用命令行来启动Java程序,可以通过编写一个本地GUI程序直接启动Java程序,这样就可避免另外打开一个命令窗口,让开发的Java程序更加专业。
JNI答应运行在虚拟机的Java程序能够与其它语言(例如C和C++)编写的程序或者类库进行相互间的调用。同时JNI提供的一整套的API,答应将Java虚拟机直接嵌入到本地的应用程序中。图1是Sun站点上对JNI的基本结构的描述。
如何在C/C++中调用Java 三联
本文将介绍如何在C/C++中调用Java方法,并结合可能涉及到的问题介绍整个开发的步骤及可能碰到的难题和解决方法。本文所采用的工具是Sun公司创建的 Java Development Kit (JDK) 版本 1.3.1,以及微软公司的Visual C++ 6开发环境。
环境搭建
为了让本文以下部分的代码能够正常工作,我们必须建立一个完整的开发环境。首先需要下载并安装JDK 1.3.1,其下载地址为“”。假设安装路径为C:JDK。下一步就是设置集成开发环境,通过Visual C++ 6的菜单Tools→Options打开选项对话框如图2。
将目录C:JDKinclude和C:JDKincludewin32加入到开发环境的Include Files目录中,同时将C:JDKlib目录添加到开发环境的Library Files目录中。这三个目录是JNI定义的一些常量、结构及方法的头文件和库文件。集成开发环境已经设置完毕,同时为了执行程序需要把Java虚拟机所用到的动态链接库所在的目录C:JDK jreinclassic设置到系统的Path环境变量中。这里需要提出的是,某些开发人员为了方便直接将JRE所用到的DLL文件直接拷贝到系统目录下。这样做是不行的,将导致初始化Java虚拟机环境失败(返回值-1),原因是Java虚拟机是以相对路径来寻找所用到的库文件和其它一些相关文件的。至此整个JNI的开发环境设置完毕,为了让此次JNI旅程能够顺利进行,还必须先预备一个Java类。在这个类中将用到Java中几乎所有有代表性的属性及方法,如静态方法与属性、数组、异常抛出与捕捉等。我们定义的Java程序(Demo.java)如下,本文中所有的代码演示都将基于该Java程序,代码如下:
package jni.test; /** * 该类是为了演示JNI如何访问各种对象属性等 * @author liudong */ public class Demo { //用于演示如何访问静态的基本类型属性 public static int COUNT = 8; //演示对象型属性 public String msg; PRivate int[] counts; public Demo() { this("缺省构造函数"); } /** * 演示如何访问构造器 */ public Demo(String msg) { System.out.println(":" + msg); this.msg = msg; this.counts = null; } /** * 该方法演示如何访问一个访问以及中文字符的处理 */ public String getMessage() { return msg; } /** * 演示数组对象的访问 */ public int[] getCounts() { return counts; } /** * 演示如何构造一个数组对象 */ public void setCounts(int[] counts) { this.counts = counts; } /** * 演示异常的捕捉 */ public void throwExcp() throws IllegalaccessException { throw new IllegalAccessException("exception occur."); } }
初始化虚拟机
本地代码在调用Java方法之前必须先加载Java虚拟机,而后所有的Java程序都在虚拟机中执行。为了初始化Java虚拟机,JNI提供了一系列的接口函数Invocation API。通过这些API可以很方便地将虚拟机加载到内存中。创建虚拟机可以用函数 jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args)。对于这个函数有一点需要注重的是,在JDK 1.1中第三个参数总是指向一个结构JDK1_ 1InitArgs, 这个结构无法完全在所有版本的虚拟机中进行无缝移植。在JDK 1.2中已经使用了一个标准的初始化结构JavaVMInitArgs来替代JDK1_1InitArgs。下面我们分别给出两种不同版本的示例代码。
在JDK 1.1初始化虚拟机:
#include int main() { JNIEnv *env; JavaVM *jvm; JDK1_1InitArgs vm_args; jint res; /* IMPORTANT: 版本号设置一定不能漏 */ 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); if (res 0) { fprintf(stderr, "Can't create Java VM "); exit(1); } /*释放虚拟机资源*/ (*jvm)-DestroyJavaVM(jvm); }
JDK 1.2初始化虚拟机:
/* invoke2.c */ #include int main() { int res; JavaVM *jvm; JNIEnv *env; JavaVMInitArgs vm_args; JavaVMOption options[3]; vm_args.version=JNI_VERSION_1_2;//这个字段必须设置为该值 /*设置初始化参数*/ options[0].optionString = "-Djava.compiler=NONE"; options[1].optionString = "-Djava.class.path=."; options[2].optionString = "-verbose:jni";//用于跟踪运行时的信息 /*版本号设置不能漏*/ vm_args.version = JNI_VERSION_1_2; vm_args.nOptions = 3; vm_args.options = options; vm_args.ignoreUnrecognized = JNI_TRUE; res = JNI_CreateJavaVM(jvm, (void**)env, vm_args); if (res 0) { fprintf(stderr, "Can't create Java VM "); exit(1); } (*jvm)-DestroyJavaVM(jvm); fprintf(stdout, "Java VM destory. "); }
为了保证JNI代码的可移植性,建议使用JDK 1.2的方法来创建虚拟机。JNI_CreateJavaVM函数的第二个参数JNIEnv *env,就是贯穿整个JNI始末的一个参数,因为几乎所有的函数都要求一个参数就是JNIEnv *env。
访问类方法
初始化了Java虚拟机后,就可以开始调用Java的方法。要调用一个Java对象的方法必须经过几个步骤:
1.获取指定对象的类定义(jclass)
有两种途径来获取对象的类定义:第一种是在已知类名的情况下使用FindClass来查找对应的类。但是要注重类名并不同于平时写的Java代码,例如要得到类jni.test.Demo的定义必须调用如下代码:
jclass cls = (*env)-FindClass(env, "jni/test/Demo");//把点号换成斜杠
然后通过对象直接得到其所对应的类定义:
jclass cls = (*env)- GetObjectClass(env, obj); //其中obj是要引用的对象,类型是jobject
2.读取要调用方法的定义(jmethodID)
我们先来看看JNI中获取方法定义的函数:
jmethodID (JNICALL *GetMethodID)(JNIEnv *env, jclass clazz, const char *name, const char *sig); jmethodID (JNICALL *GetStaticMethodID)(JNIEnv *env, jclass class, const char *name, const char *sig);
这两个函数的区别在于GetStaticMethodID是用来获取静态方法的定义,GetMethodID则是获取非静态的方法定义。这两个函数都需要提供四个参数:env就是初始化虚拟机得到的JNI环境;第二个参数class是对象的类定义,也就是第一步得到的obj;第三个参数是方法名称;最重要的是第四个参数,这个参数是方法的定义。因为我们知道Java中答应方法的多态,仅仅是通过方法名并没有办法定位到一个具体的方法,因此需要第四个参数来指定方法的具体定义。但是怎么利用一个字符串来表示方法的具体定义呢?JDK中已经预备好一个反编译工具javap,通过这个工具就可以得到类中每个属性、方法的定义。下面就来看看jni.test.Demo的定义:
打开命令行窗口并运行 javap -s -p jni.test.Demo 得到运行结果如下:
Compiled from Demo.java public class jni.test.Demo extends java.lang.Object { public static int COUNT; /* I */ public java.lang.String msg; /* Ljava/lang/String; */ private int counts[]; /* [I */ public jni.test.Demo(); /* ()V */ public jni.test.Demo(java.lang.String); /* (Ljava/lang/String;)V */ public java.lang.String getMessage(); /* ()Ljava/lang/String; */ public int getCounts()[]; /* ()[I */ public void setCounts(int[]); /* ([I)V */ public void throwExcp() throws java.lang.IllegalAccessException; /* ()V */ static {}; /* ()V */ }
我们看到类中每个属性和方法下面都有一段注释。注释中不包含空格的内容就是第四个参数要填的内容(关于javap具体参数请查询JDK的使用帮助)。下面这段代码演示如何访问jni.test.Demo的getMessage方法:
/* 假设我们已经有一个jni.test.Demo的实例obj */ jmethodID mid; jclass cls = (*env)- GetObjectClass (env, obj);//获取实例的类定义 mid=(*env)-GetMethodID(env,cls,"getMessage"," ()Ljava/lang/String; "); /*假如mid为0表示获取方法定义失败*/ jstring msg = (*env)- CallObjectMethod(env, obj, mid); /* 假如该方法是静态的方法那只需要将最后一句代码改为以下写法即可: jstring msg = (*env)- CallStaticObjectMethod(env, cls, mid); */
3.调用方法
为了调用对象的某个方法,可以使用函数CallMethod或者CallStaticMethod(访问类的静态方法),根据不同的返回类型而定。这些方法都是使用可变参数的定义,假如访问某个方法需要参数时,只需要把所有参数按照顺序填写到方法中就可以。在讲到构造函数的访问时,将演示如何访问带参数的构造函数。
访问类属性
访问类的属性与访问类的方法大体上是一致的,只不过是把方法变成属性而已。
1.获取指定对象的类(jclass)
这一步与访问类方法的第一步完全相同,具体使用参看访问类方法的第一步。
2.读取类属性的定义(jfieldID)
在JNI中是这样定义获取类属性的方法的:
jfieldID (JNICALL *GetFieldID) (JNIEnv *env, jclass clazz, const char *name, const char *sig); jfieldID (JNICALL *GetStaticFieldID) (JNIEnv *env, jclass clazz, const char *name, const char *sig);
这两个函数中第一个参数为JNI环境;clazz为类的定义;name为属性名称;第四个参数同样是为了表达属性的类型。前面我们使用javap工具获取类的具体定义的时候有这样两行:
public java.lang.String msg; /* Ljava/lang/String; */
其中第二行注释的内容就是第四个参数要填的信息,这跟访问类方法时是相同的。
3.读取和设置属性值
有了属性的定义要访问属性值就很轻易了。有几个方法用来读取和设置类的属性,它们是:GetField、SetField、GetStaticField、SetStaticField。比如读取Demo类的msg属性就可以用GetObjectField,而访问COUNT用GetStaticIntField,相关代码如下:
jfieldID field = (*env)-GetFieldID(env,obj,"msg"," Ljava/lang/String;"); jstring msg = (*env)- GetObjectField(env, cls, field);//msg就是对应Demo的msg jfieldID field2 = (*env)-GetStaticFieldID(env,obj,"COUNT","I"); jint count = (*env)-GetStaticIntField(env,cls,field2);
访问构造函数
很多人刚刚接触JNI的时候往往会在这一节碰到问题,查遍了整个jni.h看到这样一个函数NewObject,它应该是可以用来访问类的构造函数。但是该函数需要提供构造函数的方法定义,其类型是jmethodID。从前面的内容我们知道要获取方法的定义首先要知道方法的名称,但是构造函数的名称怎么来填写呢?其实访问构造函数与访问一个普通的类方法大体上是一样的,惟一不同的只是方法名称不同及方法调用时不同而已。访问类的构造函数时方法名必须填写“”。下面的代码演示如何构造一个Demo类的实例:
jclass cls = (*env)-FindClass(env, "jni/test/Demo"); /** 首先通过类的名称获取类的定义,相当于Java中的Class.forName方法 */ if (cls == 0) jmethodID mid = (*env)-GetMethodID(env,cls,"","(Ljava/lang/String;)V "); if(mid == 0) jobject demo = jenv-NewObject(cls,mid,0); /** 访问构造函数必须使用NewObject的函数来调用前面获取的构造函数的定义 上面的代码我们构造了一个Demo的实例并传一个空串null */
数组处理
创建一个新数组
要创建一个数组,我们首先应该知道数组元素的类型及数组长度。JNI定义了一批数组的类型jArray及数组操作的函数NewArray,其中就是数组中元素的类型。例如,要创建一个大小为10并且每个位置值分别为1-10的整数数组,编写代码如下:
int i = 1; jintArray array;//定义数组对象 (*env)- NewIntArray(env, 10); for(; i= 10; i++) (*env)-SetIntArrayRegion(env, array, i-1, 1, i);
访问数组中的数据
访问数组首先应该知道数组的长度及元素的类型。现在我们把创建的数组中的每个元素值打印出来,代码如下:
int i; /* 获取数组对象的元素个数 */ int len = (*env)-GetArrayLength(env, array); /* 获取数组中的所有元素 */ jint* elems = (*env)- GetIntArrayElements(env, array, 0); for(i=0; i len; i++) printf("ELEMENT %d IS %d ", i, elems[i]);
中文处理
中文字符的处理往往是让人比较头疼的事情,非凡是使用Java语言开发的软件,在JNI这个问题更加突出。由于Java中所有的字符都是Unicode编码,但是在本地方法中,例如用VC编写的程序,假如没有非凡的定义一般都没有使用Unicode的编码方式。为了让本地方法能够访问Java中定义的中文字符及Java访问本地方法产生的中文字符串,我定义了两个方法用来做相互转换。
· 方法一,将Java中文字符串转为本地字符串
/** 第一个参数是虚拟机的环境指针第二个参数为待转换的Java字符串定义第三个参数是本地存储转换后字符串的内存块第三个参数是内存块的大小 */ int JStringToChar(JNIEnv *env, jstring str, LPTSTR desc, int desc_len) { int len = 0; if(desc==NULLstr==NULL) return -1; //在VC中wchar_t是用来存储宽字节字符(UNICODE)的数据类型 wchar_t *w_buffer = new wchar_t[1024]; ZeroMemory(w_buffer,1024*sizeof(wchar_t)); //使用GetStringChars而不是GetStringUTFChars wcscpy(w_buffer,env-GetStringChars(str,0)); env-ReleaseStringChars(str,w_buffer); ZeroMemory(desc,desc_len); //调用字符编码转换函数(Win32 API)将UNICODE转为ASCII编码格式字符串 //关于函数WideCharToMultiByte的使用请参考MSDN len = WideCharToMultiByte(CP_ACP,0,w_buffer,1024,desc,desc_len,NULL,NULL); //len = wcslen(w_buffer); if(len0 len
· 方法二,将C的字符串转为Java能识别的Unicode字符串
jstring NewJString(JNIEnv* env,LPCTSTR str) { if(!env !str) return 0; int slen = strlen(str); jchar* buffer = new jchar[slen]; int len = MultiByteToWideChar(CP_ACP,0,str,strlen(str),buffer,slen); if(len0 len slen) buffer[len]=0; jstring js = env-NewString(buffer,len); delete [] buffer; return js; }
异常
由于调用了Java的方法,因此难免产生操作的异常信息。这些异常没有办法通过C++本身的异常处理机制来捕捉到,但JNI可以通过一些函数来获取Java中抛出的异常信息。之前我们在Demo类中定义了一个方法throwExcp,下面将访问该方法并捕捉其抛出来的异常信息,代码如下:
/** 假设我们已经构造了一个Demo的实例obj,其类定义为cls */ jthrowable excp = 0;/* 异常信息定义 */ jmethodID mid=(*env)-GetMethodID(env,cls,"throwExcp","()V"); /*假如mid为0表示获取方法定义失败*/ jstring msg = (*env)- CallVoidMethod(env, obj, mid); /* 在调用该方法后会有一个IllegalAccessException的异常抛出 */ excp = (*env)-ExceptionOccurred(env); if(excp){ (*env)-ExceptionClear(env); //通过访问excp来获取具体异常信息 /* 在Java中,大部分的异常信息都是扩展类java.lang.Exception,因此可以访问excp的toString 或者getMessage来获取异常信息的内容。访问这两个方法同前面讲到的如何访问类的方法是相同的。 */ }
线程和同步访问
有些时候需要使用多线程的方式来访问Java的方法。我们知道一个Java虚拟机是非常消耗系统的内存资源,差不多每个虚拟机需要内存大约在20MB左右。为了节省资源要求每个线程使用的是同一个虚拟机,这样在整个的JNI程序中只需要初始化一个虚拟机就可以了。所有人都是这样想的,但是一旦子线程访问主线程创建的虚拟机环境变量,系统就会出现错误对话框,然后整个程序终止。