在C或C++中调用JAVA方法.doc_第1页
在C或C++中调用JAVA方法.doc_第2页
在C或C++中调用JAVA方法.doc_第3页
在C或C++中调用JAVA方法.doc_第4页
在C或C++中调用JAVA方法.doc_第5页
已阅读5页,还剩9页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

在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虚拟机直接嵌入到本地的应用程序中。下图是SUN站点上对JNI的基本结构的描述:本文将介绍如何在C/C+中调用JAVA方法并将其间可能涉及到的问题串在一起介绍整个开发的步骤以及可能遇到的难题和解决方法。本文所采用的工具是Sun Microsystems 公司创建的 Java Development Kit (JDK) 版本 1.3.1以及Microsoft 公司的Visual C+ 6开发环境。一 环境搭建为了让本文以下部分的代码能够正常工作,我们必须建立一个完整的开发环境。首先需要下载并安装JDK1.3.1(可以从下载SUN公司的JDK)。我们假设安装路径为C:JDK。下一步就是设置集成开发环境,通过Visual C+ 6的菜单Tools-Options打开选项对话框如下:将目录C:JDKinclude和C:JDKincludewin32加入到开发环境的Include Files目录中,同时将C:JDKlib目录添加到开发环境的Library Files目录中,这三个目录是JNI定义的一些常量、结构以及方法的头文件和库文件。我们的集成开发环境已经设置完毕,同时为了执行程序我们需要把JAVA虚拟机所用到的动态链接库所在的目录C:JDKjrebinclassic设置到系统的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);但是这个函数有一点需要注意的是在JDK1.1中第三个参数总是指向一个结构JDK1_1InitArgs, 这个结构无法完全在所有版本的虚拟机中进行无缝移植。在JDK1.2中已经使用了一个标准的初始化结构JavaVMInitArgs来替代JDK1_1InitArgs。下面我们分别给出两种不同版本的示例代码。1 在JDK1.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 DestroyJavaVM(jvm);2 在JDK1.2初始化虚拟机 /* invoke2.c */#include int main() int res; JavaVM *jvm; JNIEnv *env;JavaVMInitArgs vm_args;JavaVMOption options3;vm_args.version=JNI_VERSION_1_2;/这个字段必须设置为该值/*设置初始化参数*/options0.optionString = -Dpiler=NONE; options1.optionString = -Djava.class.path=.; options2.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 DestroyJavaVM(jvm); fprintf(stdout, Java VM destory.n);为了保证JNI代码的可移植性,建议使用JDK1.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)是用来获取静态方法的定义,另外一个则是获取非静态的方法定义。这两个函数都需要提供四个参数:env就是初始化虚拟机得到的JNI环境;第二个参数class是对象的类定义,也就是我们第一步得到的obj;第三个参数是方法名称;最重要的是第四个参数,这个参数是方法的定义,因为我们知道JAVA中允许方法的多态,仅仅是通过方法名并没有办法定位到一个具体的方法,因此需要第四个参数来指定方法的具体定义。但是怎么利用一个字符串来表示方法的具体定义呢?不要着急,JDK中已经为我们准备好一个反编译工具javap,通过这个工具我们就可以得到类中每个属性、方法的定义。下面我们看看jni.test.Demo的定义:打开命令行窗口并运行 javap s p jni.test.Demo 得到运行结果如下:Compiled from Demo.javapublic 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用GetStaticIntFieldjfieldID field = (*env)-GetFieldID(env,obj,”msg”,” Ljava/lang/String;”);jstring msg = (*env)- GetObjectField(env, cls, field);/msg就是对应Demo的msgjfieldID 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*/六 数组处理1. 创建一个新数组:创建一个数组我们首先应该知道数组元素的类型以及数组的长度,JNI定义了一批数组的类型jArray以及数组操作的函数NewArray,其中就是数组中元素的类型。例如要创建一个大小为10并且每个位置值分别为110的整数数组代码如下:int i = 1;jintArray array;/定义数组对象(*env)- NewIntArray(env, 10);for(; iSetIntArrayRegion(env, array, i-1, 1, &i);2. 访问数组中的数据:访问数组首先应该知道数组的长度以及元素的类型。现在我们把刚才都数组中每个元素的值打印出来:int i;/* 获取数组对象的元素个数 */int len = (*env)-GetArrayLength(env, array);/* 获取数组中的所有元素 */jint* elems = (*env)- GetIntArrayElements(env, array, 0);for(i=0; iGetStringChars(str,0);env-ReleaseStringChars(str,w_buffer);ZeroMemory(desc,desc_len);/调用字符编码转换函数(Win32 API)将UNICODE转为ASCII编码格式字符串/关于函数WideCharToMultiByte的使用请参考MSDNlen = WideCharToMultiByte(CP_ACP,0,w_buffer,1024,desc,desc_len,NULL,NULL);/len = wcslen(w_buffer);if(len0 & len0 & len 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虚拟机是非常消耗系统的内存资源,差不多每个虚拟机需要内存大约在20M左右。为了节省资源我们要求每个线程使用的是同一个虚拟机,这样在整个的JNI程序中我们只需要初始化一个虚拟机就可以了。所有人都是这样想的,但是一旦子线程访问主线程创建的虚拟机环境变量的时候就会出现类似下面的错误对话框,然后整个程序终止。其实这里面涉及到两个概念分别是虚拟机(JavaVM *jvm)和虚拟机环境(JNIEnv *env)。真正消耗大量系统资源的是jvm而不是env,jvm是允许多个线程访问的,但是env只能被创建它本身的线程所访问。每个线程必须创建自己的虚拟机环境env。这时候会有人提出疑问,主线程在初始化虚拟机的时候就创建了虚拟机环境env,那么子线程如果也要创建自己的虚拟机环境是不是其实也创建了第二个虚拟机呢?为了让子线程能够创建自己的env,JNI提供了两个函数:AttachCurrentThread和DetachCurrentThread。下面代码就是子线程访问JAVA方法的框架:DWORD WINAPI ThreadProc(PVOID dwParam)JavaVM jvm = (JavaVM*)dwParam;/* 将虚拟机通过参数传入 */JNIEnv* env;(*jvm)- AttachCurrentThread(j

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论