注:chap1~13,JNI函数编写教程,其中chap5讲得好;
Chap14~,JNIEnv和多线程,其中chap17讲得好。
目录
定义
设计目的
·标准的java类库可能不支持你的程序所需的特性。
·或许你已经有了一个用其他语言写成的库或程序,而你希望在java程序中使用它。
书写步骤
·使用“javah-jnijava类名”生成扩展名为h的头文件
·使用C/C++实现本地方法
·将C/C++编写的文件生成动态连接库
·ok
1)编写java程序:这里以HelloWorld为例。
代码1:
classHelloWorld{
publicnativevoiddisplayHelloWorld();
static{
System.loadLibrary("hello");
}
publicstaticvoidmain(String[]args){
newHelloWorld().displayHelloWorld();
2)编译
没有什么好说的了javacHelloWorld.java
3)生成扩展名为h的头文件
javah-jniHelloWorld
头文件的内容:
/*DONOTEDITTHISFILE-itismachinegenerated*/
1.include
/*HeaderforclassHelloWorld*/
1.ifndef_Included_HelloWorld
2.define_Included_HelloWorld
3.ifdef__cplusplus
extern"C"{
1.endif
/*
*Class:HelloWorld
*Method:displayHelloWorld
*Signature:()V
*/
JNIEXPORTvoidJNICALLJava_HelloWorld_displayHelloWorld(JNIEnv*,jobject);
1.ifdef__cplusplus
2.endif
代码2:
1#include"jni.h"
2#include"HelloWorld.h"
3//#includeotherheaders
4JNIEXPORTvoidJNICALLJava_HelloWorld_displayHelloWorld(JNIEnv*env,jobjectobj)
{
printf("Helloworld!\n");
return;
5)生成动态库
这里以在Windows中为例,需要生成dll文件。在保存HelloWorldImpl.c文件夹下面,使用VC的编译器cl成。cl-I%java_home%\include-I%java_home%\include\win32-LDHelloWorldImp.c-Fehello.dll注意:生成的dll文件名在选项-Fe后面配置,这里是hello,因为在HelloWorld.java文件中我们loadLibary的时候使用的名字是hello。当然这里修改之后那里也需要修改。另外需要将-I%java_home%\include-I%java_home%\include\win32参数加上,因为在第四步里面编写本地方法的时候引入了jni.h文件。
6)运行程序javaHelloWorld就ok.
简要使用例子
下面是一个简单的例子实现打印一句话的功能,但是用的c的printf最终实现。一般提供给java的jni接口包括一个so文件(封装了c函数的实现)和一个java文件(需要调用path的类)。
classweiqiong{
static{System.loadLibrary("testjni");//载入静态库,test函数在其中实现
publicvoidtest()
testjni();
publicstaticvoidmain(Stringargs[])
weiqionghaha=newweiqiong();haha.test();
2.然后执行javacweiqiong.java,如果没有报错,会生成一个weiqiong.class。
JNIEXPORTvoidJNICALLJava_weiqiong_testjni(JNIEnv*,jobject);
4.创建文件testjni.c将上面那个函数实现,内容如下:
2.include
JNIEXPORTvoidJNICALLJava_weiqiong_testjni(JNIEnv*env,jobjectobj){printf("haha---------gointoc!!!\n");}
5.为了生成.so文件,创建makefile文件如下:
libtestjni.so:testjni.omakefilegcc-Wall-rdynamic-shared-olibtestjni.sotestjni.otestjni.o:testjni.cweiqiong.hgcc-Wall-ctestjni.c-I./-I/usr/java/j2sdk1.4.0/include-I/usr/java/j2sdk1.4.0/include/linuxcl:rm-rf*.o*.so注意:gcc前面是tab空,j2sdk的目录根据自己装的j2sdk的具体版本来写,生成的so文件的名字必须是loadLibrary的参数名前加“lib”。
6.exportLD_LIBRARY_PATH=.,由此设置library路径为当前目录,这样java文件才能找到so文件。一般的做法是将so文件copy到本机的LD_LIBRARY_PATH目录下。
7.执行javaweiqiong,打印出结果:“haha---------gointoc!!!”
调用中考虑的问题
在首次使用JNI的时候有些疑问,后来在使用中一一解决,下面就是这些问题的备忘:
1。java和c是如何互通的?
其实不能互通的原因主要是数据类型的问题,jni解决了这个问题,例如那个c文件中的jstring数据类型就是java传入的String对象,经过jni函数的转化就能成为c的char*。
对应数据类型关系如下表:
Java类型本地c类型说明booleanjboolean无符号,8位bytejbyte无符号,8位charjchar无符号,16位shortjshort有符号,16位intjint有符号,32位longjlong有符号,64位floatjfloat32位doublejdouble64位voidvoidN/A
JNI还包含了很多对应于不同Java对象的引用类型如下图:
2.如何将java传入的String参数转换为c的char*,然后使用
3.将c中获取的一个char*的buffer传递给java?
这个char*如果是一般的字符串的话,作为string传回去就可以了。如果是含有’\0’的buffer,最好作为bytearray传出,因为可以制定copy的length,如果copy到string,可能到’\0’就截断了。
有两种方式传递得到的数据:
一种是在jni中直接new一个byte数组,然后调用函数(*env)->SetByteArrayRegion(env,bytearray,0,len,buffer);将buffer的值copy到bytearray中,函数直接returnbytearray就可以了。
一种是return错误号,数据作为参数传出,但是java的基本数据类型是传值,对象是传递的引用,所以将这个需要传出的byte数组用某个类包一下,如下:
classRetObj{publicbyte[]bytearray;}这个对象作为函数的参数retobj传出,通过如下函数将retobj中的byte数组赋值便于传出。代码如下:
jclasscls;
jfieldIDfid;
jbyteArraybytearray;
bytearray=(*env)->NewByteArray(env,len);
(*env)->SetByteArrayRegion(env,bytearray,0,len,buffer);
cls=(*env)->GetObjectClass(env,retobj);
fid=(*env)->GetFieldID(env,cls,"retbytes","[B"]);
(*env)->SetObjectField(env,retobj,fid,bytearray);
4.不知道占用多少空间的buffer,如何传递出去呢?
在jni的c文件中new出空间,传递出去。java的数据不初始化,指向传递出去的空间即可。
对JAVA传入数据的处理
1.如果传入的是bytearray的话,作如下处理得到buffer:
char*tmpdata=(char*)(*env)->GetByteArrayElements(env,bytearray,NULL);
(*env)->ReleaseByteArrayElements(env,bytearray,tmpdata,0);
D:\ProgramFiles\Java\jdk1.6.0_12\bin>javah
用法:javah[选项]<类>
其中[选项]包括:
-help输出此帮助消息并退出
-classpath<路径>用于装入类的路径
-bootclasspath<路径>用于装入引导类的路径
-d<目录>输出目录
-o<文件>输出文件(只能使用-d或-o中的一个)
-jni生成JNI样式的头文件(默认)
-version输出版本信息
-verbose启用详细输出
然后依据头文件的内容编写com_hode_hodeframework_modelupdate_CheckFile.c文件
#include"CheckFile.h"#include#includeJNIEXPORTvoidJNICALLJava_com_hode_hodeframework_modelupdate_CheckFile_displayHelloWorld(JNIEnv*env,jobjectobj){printf("Helloworld!");return;}之后编译生成DLL文件如“test.dll”,名称与System.loadLibrary("test")中的名称一致vc的编译办法:cl-I%java_home%include-I%java_home%includewin32-LDcom_hode_hodeframework_modelupdate_CheckFile.c-Fetest.dll最后在运行时加参数-Djava.library.path=[dll寄存的路径]
JNI允许Java代码使用以其它语言(譬如C和C++)编写的代码和代码库。InvocationAPI(JNI的一部分)可以用来将Java虚拟机(JVM)嵌入到本机应用程序中,从而允许程序员从本机代码内部调用Java代码。
我们还将讲述一些高级主题,包括本机方法的异常处理和多线程。要充分理解本教程,您应该熟悉Java平台的安全性模型,并有一些多线程应用程序开发的经验。
这里将关于高级主题的节从较基本的循序渐进JNI简介中划分出来。现在,初级Java程序员可以先学习本教程的前两部分,掌握之后再开始学习高级主题。
要运行本教程中的示例,您需要下列工具与组件:
虽然您可以使用自己喜欢的任何开发环境,但我们将在本教程中使用示例是用随SDK一起提供的标准工具和组件编写的。请参阅参考资料来下载SDK、完整的源文件以及对于完成本教程不可缺少的其它工具。本教程具体地解释了Sun的JNI实现,该实现被认为是JNI解决方案的标准。本教程中没有讨论其它JNI实现的详细信息。
在Java2SDK中,JVM和运行时支持位于名为jvm.dll(Windows)或libjvm.so(UNIX)的共享库文件中。在Java1.1JDK中,JVM和运行时支持位于名为javai.dll(Windows)或libjava.so(UNIX)的共享库文件中。版本1.1的共享库包含运行时以及类库的一些本机方法,但在版本1.2中已经不包含运行时,并且本机方法被放在java.dll和libjava.so中。对于以下Java代码,这一变化很重要:
在两种情况下,在您的本机库能与版本1.2一起使用之前,都必须重新链接它们。注:这个变化应该不影响JNI程序员实现本机方法—只有通过InvocationAPI调用JVM的JNI代码才会受到影响。
如果使用随SDK/JDK一起提供的jni.h文件,则头文件将使用SDK/JDK安装目录中的缺省JVM(jvm.dll或libjvm.so)。支持JNI的Java平台的任何实现都会这么做,或允许您指定JVM共享库;然而,完成这方面操作的细节可能会因具体Java平台/JVM实现而有所不同。实际上,许多JVM实现根本不支持JNI。
用Java调用C/C++代码
当无法用Java语言编写整个应用程序时,JNI允许您使用本机代码。在下列典型情况下,您可能决定使用本机代码:
从Java代码调用C/C++的六个步骤
从Java程序调用C或C++代码的过程由六个步骤组成。我们将在下面几页中深入讨论每个步骤,但还是先让我们迅速地浏览一下它们。
步骤1:编写Java代码
这里是名为Sample1.java的Java源代码文件的示例:
packagecom.ibm.course.jni;
publicclassSample1{
publicnativeintintMethod(intn);
publicnativebooleanbooleanMethod(booleanbool);
publicnativeStringstringMethod(Stringtext);
publicnativeintintArrayMethod(int[]intArray);
System.loadLibrary("Sample1");
Sample1sample=newSample1();
intsquare=sample.intMethod(5);
booleanbool=sample.booleanMethod(true);
Stringtext=sample.stringMethod("JAVA");
intsum=sample.intArrayMethod(newint[]{1,1,2,3,5,8,13});
System.out.println("intMethod:"+square);
System.out.println("booleanMethod:"+bool);
System.out.println("stringMethod:"+text);
System.out.println("intArrayMethod:"+sum);
这段代码做了些什么?
现在,让我们逐行研究一下代码:
注:基于UNIX的平台上的共享库文件通常含有前缀“lib”。在本例中,第10行可能是System.loadLibrary("libSample1");。请一定要注意您在步骤5:创建共享库文件中生成的共享库文件名。
步骤2:编译Java代码
接下来,我们需要将Java代码编译成字节码。完成这一步的方法之一是使用随SDK一起提供的Java编译器javac。用来将Java代码编译成字节码的命令是:
C:\eclipse\workspace\IBMJNI\src\com\ibm\course\jni>javacSample1.java
步骤3:创建C/C++头文件
第三步是创建C/C++头文件,它定义本机函数说明。完成这一步的方法之一是使用javah.exe,它是随SDK一起提供的本机方法C存根生成器工具。这个工具被设计成用来创建头文件,该头文件为在Java源代码文件中所找到的每个native方法定义C风格的函数。这里使用的命令是:
C:\eclipse\workspace\IBMJNI\bin>javah–classpath./–jnicom.ibm.course.jni.Sample1
javah工具帮助
Usage:javah[options]
where[options]include:
-helpPrintthishelpmessageandexit
-classpath
-bootclasspath
-d
-o
-jniGenerateJNI-styleheaderfile(default)
-versionPrintversioninformation
-verboseEnableverboseoutput
-forceAlwayswriteoutputfiles
instance,java.lang.Object).
在Sample1.java上运行javah.exe的结果
下面的Sample1.h是对我们的Java代码运行javah工具所生成的C/C++头文件:
#include
/*Headerforclasscom_ibm_course_jni_Sample1*/
#ifndef_Included_com_ibm_course_jni_Sample1
#define_Included_com_ibm_course_jni_Sample1
#ifdef__cplusplus
#endif
*Class:com_ibm_course_jni_Sample1
*Method:intMethod
*Signature:(I)I
JNIEXPORTjintJNICALLJava_com_ibm_course_jni_Sample1_intMethod
(JNIEnv*,jobject,jint);
*Method:booleanMethod
*Signature:(Z)Z
JNIEXPORTjbooleanJNICALLJava_com_ibm_course_jni_Sample1_booleanMethod
(JNIEnv*,jobject,jboolean);
*Method:stringMethod
*Signature:(Ljava/lang/String;)Ljava/lang/String;
JNIEXPORTjstringJNICALLJava_com_ibm_course_jni_Sample1_stringMethod
(JNIEnv*,jobject,jstring);
*Method:intArrayMethod
*Signature:([I)I
JNIEXPORTjintJNICALLJava_com_ibm_course_jni_Sample1_intArrayMethod
(JNIEnv*,jobject,jintArray);
关于C/C++头文件
jobject参数引用当前对象。因此,如果C或C++代码需要引用Java函数,则这个jobject充当引用或指针,返回调用的Java对象。函数名本身是由前缀“Java_”加全限定类名,再加下划线和方法名构成的。
JNI类型
JNI使用几种映射到Java类型的本机定义的C类型。这些类型可以分成两类:原始类型和伪类(pseudo-classes)。在C中,伪类作为结构实现,而在C++中它们是真正的类。
Java原始类型直接映射到C依赖于平台的类型,如下所示:
C类型jarray表示通用数组。在C中,所有的数组类型实际上只是jobject的同义类型。但是,在C++中,所有的数组类型都继承了jarray,jarray又依次继承了jobject。下列表显示了Java数组类型是如何映射到JNIC数组类型的。
步骤4:编写C/C++代码
C函数实现
以下是Sample1.c,它是用C编写的实现:
#include"com_ibm_course_jni_Sample1.h"
#include
(JNIEnv*env,jobjectobj,jintnum){
returnnum*num;
(JNIEnv*env,jobjectobj,jbooleanboolean){
return!boolean;
(JNIEnv*env,jobjectobj,jstringstring){
constchar*str=(*env)->GetStringUTFChars(env,string,0);
charcap[128];
strcpy(cap,str);
(*env)->ReleaseStringUTFChars(env,string,str);
return(*env)->NewStringUTF(env,strupr(cap));
(JNIEnv*env,jobjectobj,jintArrayarray){
inti,sum=0;
jsizelen=(*env)->GetArrayLength(env,array);
jint*body=(*env)->GetIntArrayElements(env,array,0);
for(i=0;i {sum+=body[i]; (*env)->ReleaseIntArrayElements(env,array,body,0); returnsum; voidmain(){} C++函数实现 以下是Sample1.cpp(C++实现) JNIEXPORTjintJNICALLJava_Sample1_intMethod JNIEXPORTjbooleanJNICALLJava_Sample1_booleanMethod JNIEXPORTjstringJNICALLJava_Sample1_stringMethod constchar*str=env->GetStringUTFChars(string,0); env->ReleaseStringUTFChars(string,str); returnenv->NewStringUTF(strupr(cap)); JNIEXPORTjintJNICALLJava_Sample1_intArrayMethod jsizelen=env->GetArrayLength(array); jint*body=env->GetIntArrayElements(array,0); env->ReleaseIntArrayElements(array,body,0); C和C++函数实现的比较 唯一的差异在于用来访问JNI函数的方法。在C中,JNI函数调用由“(*env)->”作前缀,目的是为了取出函数指针所引用的值。在C++中,JNIEnv类拥有处理函数指针查找的内联成员函数。下面将说明这个细微的差异,其中,这两行代码访问同一函数,但每种语言都有各自的语法。 C语法:jsizelen=(*env)->GetArrayLength(env,array); C++语法:jsizelen=env->GetArrayLength(array); 步骤5:创建共享库文件 接下来,我们创建包含本机代码的共享库文件。大多数C和C++编译器除了可以创建机器代码可执行文件以外,也可以创建共享库文件。用来创建共享库文件的命令取决于您使用的编译器。下面是在Windows和Solaris系统上执行的命令。 Windows:cl-Ic:\jdk\include-Ic:\jdk\include\win32-LDSample1.c-FeSample1.dll Solaris:cc-G-I/usr/local/jdk/include-I/user/local/jdk/include/solarisSample1.c-oSample1.so 步骤6:运行Java程序 最后一步是运行Java程序,并确保代码正确工作。因为必须在Java虚拟机中执行所有Java代码,所以需要使用Java运行时环境。完成这一步的方法之一是使用java,它是随SDK一起提供的Java解释器。所使用的命令是: javacom.ibm.course.jni.Sample1 当运行Sample1.class程序时,应该获得下列结果: PROMPT>javaSample1 intMethod:25 booleanMethod:false stringMethod:JAVA intArrayMethod:33 PROMPT> 故障排除 当使用JNI从Java程序访问本机代码时,您会遇到许多问题。您会遇到的三个最常见的错误是: 从Java调用C或C++本机代码(虽然不简单)是Java平台中一种良好集成的功能。虽然JNI支持C和C++,但C++接口更清晰一些并且通常比C接口更可取。 正如您已经看到的,调用C或C++本机代码需要赋予函数特殊的名称,并创建共享库文件。当利用现有代码库时,更改代码通常是不可取的。要避免这一点,在C++中,通常创建代理代码或代理类,它们有专门的JNI所需的命名函数。然后,这些函数可以调用底层库函数,这些库函数的说明和实现保持不变。 作为主调方的Java源程序TestJNI.java如下。 代码清单15-4在Linux平台上调用C函数的例程——TestJNI.java 1.publicclassTestJNI 2.{ 3.static 4.{ 5.System.loadLibrary("testjni");//载入静态库,test函数在其中实现 6.} 7. 9. 10.publicvoidtest() 11.{ 12.testjni(); 13.} 14. 15.publicstaticvoidmain(Stringargs[]) 16.{ 17.TestJNIhaha=newTestJNI(); 18.haha.test(); 19.} 20.} 在Linux平台上,遵循JNI规范的动态链接库文件名必须以“lib”开头。例如在上面的Java程序中指定的库文件名为“testjni”,则实际的库文件应该命名为“libtestjni.so”。 编译TestJNI.java,并为C程序生成头文件: javacTestJNI.java javahTestJNI 提供testjni()函数的testjni.c源文件如下。 代码清单15-5在Linux平台上调用C函数的例程——testjni.c #include #include JNIEXPORTvoidJNICALLJava_TestJNI_testjni(JNIEnv*env,jobjectobj){ printf("haha---------gointoc!!!\n"); 编写Makefile文件如下,JDK安装的位置请读者自行调整: libtestjni.so:testjni.o gcc-rdynamic-shared-olibtestjni.sotestjni.o testjni.o:testjni.cTestJNI.h gcc-ctestjni.c-I./-I/usr/java/jdk1.6.0_00/include-I/usr/java/jdk1.6.0_00/include/linux 在Makefile文件中,我们描述了最终的libtestjin.so依赖于目标文件testjni.o,而testjni.o则依赖于testjni.c源文件和TestJNI.h头文件。请注意,我们在将testjni.o连接成动态链接库文件时使用了“-rdynamic”选项。 执行make命令编译testjni.c。Linux平台和在Windows平台上类似,有3种方法可以让Java程序找到并装载动态链接库文件。 —将动态链接库文件放置在当前路径下。 —将动态链接库文件放置在LD_LIBRARY_PATH环境变量所指向的路径下。注意这一点和Windows平台稍有区别,Windows平台参考PATH环境变量。 —在启动JVM时指定选项“-Djava.library.path”,将动态链接库文件放置在该选项所指向的路径下。 从下一节开始,我们开始接触到在JNI框架内Java调用C程序的一些高级话题,包括如何传递参数、如何传递数组、如何传递对象等。 到目前为止,我们还没有实现Java程序向C程序传递参数,或者C程序向Java程序传递参数。本例程将由Java程序向C程序传入一个字符串,C程序对该字符串转成大写形式后回传给Java程序。 Java源程序如下。 代码清单15-6在Linux平台上调用C函数的例程——Sample1 publicclassSample1 publicstaticvoidmain(String[]args) Stringtext=sample.stringMethod("ThinkingInJava"); Sample1.java以“ThinkingInJava”为参数调用libSample1.so中的函数stringMethod(),在得到返回的字符串后打印输出。 Sample1.c的源程序如下。 代码清单15-7在Linux平台上调用C函数的例程——Sample1.c #include JNIEXPORTjstringJNICALLJava_Sample1_stringMethod(JNIEnv*env,jobjectobj,jstringstring) inti=0; for(i=0;i *(cap+i)=(char)toupper(*(cap+i)); return(*env)->NewStringUTF(env,cap); 首先请注意函数头部分,函数接收一个jstring类型的输入参数,并输出一个jstring类型的参数。jstring是jni.h中定义的数据类型,是JNI框架内特有的字符串类型,因为jni.h在Sample1.h中被引入,因此在Sample1.c中无须再次引入。 程序的第4行是从JNI调用上下文中获取UTF编码的输入字符,将其放在指针str所指向的一段内存中。第9行是释放这段内存。第13行是将经过大写转换的字符串予以返回,这一句使用了NewStringUTF()函数,将C语言的字符串指针转换为JNI的jstring类型。JNIEnv也是在jni.h中定义的,代表JNI调用的上下文,GetStringUTFChars()、ReleaseStringUTFChars()和NewStringUTF()均是JNIEnv的函数。 15.2.2.4传递整型数组 本节例程将首次尝试在JNI框架内启用数组:C程序向Java程序返回一个定长的整型数组成的数组,Java程序将该数组打印输出。 Java程序的源代码如下。 代码清单15-8在Linux平台上调用C函数的例程——Sample2 publicclassSample2 publicnativeint[]intMethod(); System.loadLibrary("Sample2"); Sample2sample=newSample2(); int[]nums=sample.intMethod(); for(inti=0;i System.out.println(nums[i]); Sample2.java调用libSample2.so中的函数intMethod()。Sample2.c的源代码如下。 代码清单15-9在Linux平台上调用C函数的例程——Sample2.c #include JNIEXPORTjintArrayJNICALLJava_Sample2_intMethod(JNIEnv*env,jobjectobj) inti=1; jintArrayarray;//定义数组对象 array=(*env)->NewIntArray(env,10); for(;i<=10;i++) (*env)->SetIntArrayRegion(env,array,i-1,1,&i); /*获取数组对象的元素个数*/ intlen=(*env)->GetArrayLength(env,array); /*获取数组中的所有元素*/ jint*elems=(*env)->GetIntArrayElements(env,array,0); printf("ELEMENT%dIS%d\n",i,elems[i]); returnarray; 15.2.2.5传递字符串数组 本节例程是对上节例程的进一步深化:虽然仍然是传递数组,但是数组的基类换成了字符串这样一种对象数据类型。Java程序将向C程序传入一个包含中文字符的字符串,C程序并没有处理这个字符串,而是开辟出一个新的字符串数组返回给Java程序,其中还包含两个汉字字符串。 代码清单15-10在Linux平台上调用C函数的例程——Sample3 publicclassSample3 publicnativeString[]stringMethod(Stringtext); throwsjava.io.UnsupportedEncodingException System.loadLibrary("Sample3"); Sample3sample=newSample3(); String[]texts=sample.stringMethod("java编程思想"); for(inti=0;i texts[i]=newString(texts[i].getBytes("ISO8859-1"),"GBK"); System.out.print(texts[i]); System.out.println(); Sample3.java调用libSample3.so中的函数stringMethod()。Sample3.c的源代码如下: 代码清单15-11在Linux平台上调用C函数的例程——Sample3.c #include #include #defineARRAY_LENGTH5 JNIEXPORTjobjectArrayJNICALLJava_Sample3_stringMethod (JNIEnv*env,jobjectobj,jstringstring) jclassobjClass=(*env)->FindClass(env,"java/lang/String"); jobjectArraytexts=(*env)->NewObjectArray(env, (jsize)ARRAY_LENGTH,objClass,0); jstringjstr; char*sa[]={"Hello,","world!","JNI","很","好玩"}; for(;i jstr=(*env)->NewStringUTF(env,sa[i]); (*env)->SetObjectArrayElement(env,texts,i,jstr);//必须放入jstring returntexts; 在例程中我们定义了一个长度为5的对象数组texts,并在程序的第18行向其中循环放入预先定义好的sa数组中的字符串,当然前置条件是使用NewStringUTF()函数将C语言的字符串转换为jstring类型。 15.2.2.6传递对象数组 本节例程演示的是C程序向Java程序传递对象数组,而且对象数组中存放的不再是字符串,而是一个在Java中自定义的、含有一个topic属性的MailInfo对象类型。 MailInfo对象定义如下。 代码清单15-12在Linux平台上调用C函数的例程——MailInfo publicclassMailInfo{ publicStringtopic; publicStringgetTopic() returnthis.topic; publicvoidsetTopic(Stringtopic) this.topic=topic; 代码清单15-13在Linux平台上调用C函数的例程——Sample4 publicclassSample4 publicnativeMailInfo[]objectMethod(Stringtext); System.loadLibrary("Sample4"); Sample4sample=newSample4(); MailInfo[]mails=sample.objectMethod("ThinkingInJava"); for(inti=0;i System.out.println(mails[i].topic); Sample4.java调用libSample4.so中的objectMethod()函数。Sample4.c的源代码如下。 代码清单15-14在Linux平台上调用C函数的例程——Sample4.c #include JNIEXPORTjobjectArrayJNICALLJava_Sample4_objectMethod( JNIEnv*env,jobjectobj,jstringstring) jclassobjClass=(*env)->FindClass(env,"java/lang/Object"); jobjectArraymails=(*env)->NewObjectArray(env, jclassobjectClass=(*env)->FindClass(env,"MailInfo"); jfieldIDtopicFieldId=(*env)->GetFieldID(env,objectClass, "topic","Ljava/lang/String;"); (*env)->SetObjectField(env,obj,topicFieldId,string); (*env)->SetObjectArrayElement(env,mails,i,obj); returnmails; 程序的第9、10行读者们应该不会陌生,在上一节的例程中已经出现过,不同之处在于这次通过FindClass()函数在JNI上下文中获取的是java.lang.Object的类型(Class),并将其作为基类开辟出一个长度为5的对象数组,准备用来存放MailInfo对象。 程序的第12、13行的目的则是创建一个jfieldID类型的变量,在JNI中,操作对象属性都是通过jfieldID进行的。第12行首先查找得到MailInfo的类型(Class),然后基于这个jclass进一步获取其名为topic的属性,并将其赋予jfieldID变量。 程序的第18、19行的目的是循环向对象数组中放入jobject对象。SetObjectField()函数属于首次使用,该函数的作用是向jobject的属性赋值,而值的内容正是Java程序传入的jstring变量值。请注意在向对象属性赋值和向对象数组中放入对象的过程中,我们使用了在函数头部分定义的jobject类型的环境参数obj作为中介。至此,JNI框架固有的两个环境入参env和obj,我们都有涉及。 <利用VC++6.0实现JNI的最简单的例子> 这些资料的例子中,大多数只是输入一些简单的参数,获取没有参数。而在实际的使用过程中,往往需要对参数进行处理转换。才可以被C/C++程序识别。比如我们在C++中有一个结构(Struct)DiskInfo,需要传递一个类似于DiskInfo*pDiskInfo的参数,类似于在C++这样参数如何传递到Java中呢?下面我们就来讨论C++到Java中方法的一些常见参数的转换: 1.定义NativeJava类: //硬盘信息 struct{ charname[256]; intserial; }DiskInfo; classDiskInfo{ //名字 publicStringname; //序列号 publicintserial; 在这个类中,申明一些Native的本地方法,来测试方法参数的传递,分别定义了一些函数,用来传递结构或者结构数组,具体定义如下面代码: /**//******************定义本地方法********************///输入常用的数值类型(Boolean,Byte,Char,Short,Int,Float,Double)publicnativevoiddisplayParms(StringshowText,inti,booleanbl); //调用一个静态方法publicnativeintadd(inta,intb); //输入一个数组publicnativevoidsetArray(boolean[]blList); //返回一个字符串数组publicnativeString[]getStringArray(); //返回一个结构publicnativeDiskInfogetStruct(); //返回一个结构数组publicnativeDiskInfo[]getStructArray(); 2.编译生成C/C++头文件 定义好了Java类之后,接下来就要写本地代码。本地方法符号提供一个满足约定的头文件,使用Java工具Javah可以很容易地创建它而不用手动去创建。你对Java的class文件使用javah命令,就会为你生成一个对应的C/C++头文件。 1)、在控制台下进入工作路径,本工程路径为:E:\work\java\workspace\JavaJni。 2)、运行javah命令:javah-classpathE:\work\java\workspace\JavaJnicom.sundy.jnidemoChangeMethodFromJni 本文生成的C/C++头文件名为:com_sundy_jnidemo_ChangeMethodFromJni.h 3.在C/C++中实现本地方法 生成C/C++头文件之后,你就需要写头文件对应的本地方法。注意:所有的本地方法的第一个参数都是指向JNIEnv结构的。这个结构是用来调用JNI函数的。第二个参数jclass的意义,要看方法是不是静态的(static)或者实例(Instance)的。前者,jclass代表一个类对象的引用,而后者是被调用的方法所属对象的引用。 返回值和参数类型根据等价约定映射到本地C/C++类型,如表JNI类型映射所示。有些类型,在本地代码中可直接使用,而其他类型只有通过JNI调用操作。 表A※JNI类型映射 Java类型本地类型描述 booleanjbooleanC/C++8位整型 bytejbyteC/C++带符号的8位整型 charjcharC/C++无符号的16位整型 shortjshortC/C++带符号的16位整型 intjintC/C++带符号的32位整型 longjlongC/C++带符号的64位整型e floatjfloatC/C++32位浮点型 doublejdoubleC/C++64位浮点型 Objectjobject任何Java对象,或者没有对应java类型的对象 ClassjclassClass对象 Stringjstring字符串对象 Object[]jobjectArray任何对象的数组 boolean[]jbooleanArray布尔型数组 byte[]jbyteArray比特型数组 char[]jcharArray字符型数组 short[]jshortArray短整型数组 int[]jintArray整型数组 long[]jlongArray长整型数组 float[]jfloatArray浮点型数组 double[]jdoubleArray双浮点型数组 3.1使用数组: JNI通过JNIEnv提供的操作Java数组的功能。它提供了两个函数:一个是操作java的简单型数组的,另一个是操作对象类型数组的。 因为速度的原因,简单类型的数组作为指向本地类型的指针暴露给本地代码。因此,它们能作为常规的数组存取。这个指针是指向实际的Java数组或者Java数组的拷贝的指针。另外,数组的布置保证匹配本地类型。 为了存取Java简单类型的数组,你就要要使用GetXXXArrayElements函数(见表B),XXX代表了数组的类型。这个函数把Java数组看成参数,返回一个指向对应的本地类型的数组的指针。 表B 函数Java数组类型本地类型 GetBooleanArrayElementsjbooleanArrayjboolean GetByteArrayElementsjbyteArrayjbyte GetCharArrayElementsjcharArrayjchar GetShortArrayElementsjshortArrayjshort GetIntArrayElementsjintArrayjint GetLongArrayElementsjlongArrayjlong GetFloatArrayElementsjfloatArrayjfloat GetDoubleArrayElementsjdoubleArrayjdouble JNI数组存取函数 为了使用java对象的数组,你必须使用GetObjectArrayElement函数和SetObjectArrayElement函数,分别去get,set数组的元素。GetArrayLength函数会返回数组的长度。 3.2使用对象 JNI提供的另外一个功能是在本地代码中使用Java对象。通过使用合适的JNI函数,你可以创建Java对象,get、set静态(static)和实例(instance)的域,调用静态(static)和实例(instance)函数。JNI通过ID识别域和方法,一个域或方法的ID是任何处理域和方法的函数的必须参数。 表C列出了用以得到静态(static)和实例(instance)的域与方法的JNI函数。每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对应返回的jfieldID或jmethodID。 表C 函数描述 GetFieldID得到一个实例的域的ID GetStaticFieldID得到一个静态的域的ID GetMethodID得到一个实例的方法的ID GetStaticMethodID得到一个静态方法的ID ※域和方法的函数 如果你有了一个类的实例,它就可以通过方法GetObjectClass得到,或者如果你没有这个类的实例,可以通过FindClass得到。符号是从域的类型或者方法的参数,返回值得到字符串,如表D所示。 表D Java类型符号 booleanZ byteB charC shortS intI longL floatF doubleD voidV objects对象Lfully-qualified-class-name;L类名 Arrays数组[array-type[数组类型 methods方法(argument-types)return-type(参数类型)返回类型 ※确定域和方法的符号 下面我们来看看,如果通过使用数组和对象,从C++中的获取到Java中的DiskInfo类对象,并返回一个DiskInfo数组: //返回一个结构数组,返回一个硬盘信息的结构数组 JNIEXPORTjobjectArrayJNICALL Java_com_sundy_jnidemo_ChangeMethodFromJni_getStructArray (JNIEnv*env,jobject_obj) //申明一个object数组 jobjectArrayargs=0; //数组大小 jsizelen=5; //获取object所属类,一般为java/lang/Object就可以了 jclassobjClass=(env)->FindClass("java/lang/Object"); //新建object数组 args=(env)->NewObjectArray(len,objClass,0); /**//*下面为获取到Java中对应的实例类中的变量*/ //获取Java中的实例类 jclassobjectClass=(env)->FindClass("com/sundy/jnidemo/DiskInfo"); //获取类中每一个变量的定义 jfieldIDstr=(env)->GetFieldID(objectClass,"name","Ljava/lang/String;"); jfieldIDival=(env)->GetFieldID(objectClass,"serial","I"); //给每一个实例的变量付值,并且将实例作为一个object,添加到objcet数组中 for(inti=0;i //给每一个实例的变量付值 jstringjstr=WindowsTojstring(env,"我的磁盘名字是D:"); //(env)->SetObjectField(_obj,str,(env)->NewStringUTF("mynameisD:")); (env)->SetObjectField(_obj,str,jstr); (env)->SetShortField(_obj,ival,10); //添加到objcet数组中 (env)->SetObjectArrayElement(args,i,_obj); //返回object数组 returnargs; 全部的C/C++方法实现代码如下: /**//* * /**//** *TODOJni中一个从Java到C/C++参数传递测试类 *@author刘正伟(sundy) *@seemailto:sundy26@126.com *@version1.0 *@since2005-4-30 *修改记录: *日期修改人描述 *---------------------------------------------------------------------------------------------- //JniManage.cpp:定义DLL应用程序的入口点。 // packagecom.sundy.jnidemo; #include"stdafx.h" #include #include"jni.h" #include"jni_md.h" #include"./head/Base.h" #include"head/wmi.h" #include"head/com_sundy_jnidemo_ChangeMethodFromJni.h"//通过javah–jnijavactransfer生成 #include"stdlib.h" #include"string.h" #pragmacomment(lib,"BaseInfo.lib") #pragmacomment(lib,"jvm.lib") /**//*BOOLAPIENTRYDllMain(HANDLEhModule, DWORDul_reason_for_call, LPVOIDlpReserved ) LPTSTRstrName=newCHAR[256]; (*GetHostName)(strName); printf("%s\n",strName); delete[]strName; returnTRUE; }*/ //将jstring类型转换成windows类型 char*jstringToWindows(JNIEnv*env,jstringjstr); //将windows类型转换成jstring类型 jstringWindowsTojstring(JNIEnv*env,char*str); //主函数 BOOLWINAPIDllMain(HANDLEhHandle,DWORDdwReason,LPVOIDlpReserved) //输入常用的数值类型Boolean,Byte,Char,Short,Int,Float,Double JNIEXPORTvoidJNICALLJava_com_sundy_jnidemo_ChangeMethodFromJni_displayParms (JNIEnv*env,jobjectobj,jstrings,jinti,jbooleanb) constchar*szStr=(env)->GetStringUTFChars(s,0); printf("String=[%s]\n",szStr); printf("int=%d\n",i); printf("boolean=%s\n",(b==JNI_TRUE"true":"false")); (env)->ReleaseStringUTFChars(s,szStr); //调用一个静态方法,只有一个简单类型输出 JNIEXPORTjintJNICALLJava_com_sundy_jnidemo_ChangeMethodFromJni_add (JNIEnv*env,jobject,jinta,jintb) intrtn=(int)(a+b); return(jint)rtn; /**/////输入一个数组,这里输入的是一个Boolean类型的数组 JNIEXPORTvoidJNICALLJava_com_sundy_jnidemo_ChangeMethodFromJni_setArray (JNIEnv*env,jobject,jbooleanArrayba) jboolean*pba=(env)->GetBooleanArrayElements(ba,0); jsizelen=(env)->GetArrayLength(ba); //changeevenarrayelements for(i=0;i pba[i]=JNI_FALSE; printf("boolean=%s\n",(pba[i]==JNI_TRUE"true":"false")); (env)->ReleaseBooleanArrayElements(ba,pba,0); /**/////返回一个字符串数组 JNIEXPORTjobjectArrayJNICALLJava_com_sundy_jnidemo_ChangeMethodFromJni_getStringArray (JNIEnv*env,jobject) jstringstr; char*sa[]={"Hello,","world!","JNI","is","fun"}; args=(env)->NewObjectArray(len,(env)->FindClass("java/lang/String"),0); str=(env)->NewStringUTF(sa[i]); (env)->SetObjectArrayElement(args,i,str); //返回一个结构,这里返回一个硬盘信息的简单结构类型 JNIEXPORTjobjectJNICALLJava_com_sundy_jnidemo_ChangeMethodFromJni_getStruct (JNIEnv*env,jobjectobj) (env)->SetObjectField(obj,str,(env)->NewStringUTF("mynameisD:")); (env)->SetShortField(obj,ival,10); returnobj; JNIEXPORTjobjectArrayJNICALLJava_com_sundy_jnidemo_ChangeMethodFromJni_getStructArray //获取object所属类,一般为ava/lang/Object就可以了 char*jstringToWindows(JNIEnv*env,jstringjstr) intlength=(env)->GetStringLength(jstr); constjchar*jcstr=(env)->GetStringChars(jstr,0); char*rtn=(char*)malloc(length*2+1); intsize=0; size=WideCharToMultiByte(CP_ACP,0,(LPCWSTR)jcstr,length,rtn,(length*2+1),NULL,NULL); if(size<=0) returnNULL; (env)->ReleaseStringChars(jstr,jcstr); rtn[size]=0; returnrtn; jstringWindowsTojstring(JNIEnv*env,char*str) jstringrtn=0; intslen=strlen(str); unsignedshort*buffer=0; if(slen==0) rtn=(env)->NewStringUTF(str); else intlength=MultiByteToWideChar(CP_ACP,0,(LPCSTR)str,slen,NULL,0); buffer=(unsignedshort*)malloc(length*2+1); if(MultiByteToWideChar(CP_ACP,0,(LPCSTR)str,slen,(LPWSTR)buffer,length)>0) rtn=(env)->NewString((jchar*)buffer,length); if(buffer) free(buffer); Java测试native代码 这没有什么多说的,看代码吧 //主测试程序 ChangeMethodFromJnichangeJni=newChangeMethodFromJni(); //输入常用的数值类型(stringintboolean) System.out .println("------------------输入常用的数值类型(stringintboolean)-----------"); changeJni.displayParms("HelloWorld!",100,true); //调用一个静态方法 System.out.println("------------------调用一个静态方法-----------"); intret=changeJni.add(12,20); System.out.println("Theresultis:"+String.valueOf(ret)); //输入一个数组 System.out.println("------------------输入一个数组-----------"); boolean[]blList=newboolean[]{true,false,true}; changeJni.setArray(blList); //返回一个字符串数组 System.out.println("------------------返回一个字符串数组-----------"); String[]strList=changeJni.getStringArray(); for(inti=0;i System.out.print(strList[i]); System.out.println("------------------返回一个结构-----------"); //返回一个结构 DiskInfodisk=changeJni.getStruct(); System.out.println("name:"+disk.name); System.out.println("Serial:"+disk.serial); //返回一个结构数组 System.out.println("------------------返回一个结构数组-----------"); DiskInfo[]diskList=changeJni.getStructArray(); for(inti=0;i System.out.println("name:"+diskList[i].name); System.out.println("Serial:"+diskList[i].serial); 注:本程序在VS2003,eclipse(jse5.0)winxpsp2编译通过 #re:Jni中C++和Java的参数传递2005-05-2214:35张磊 #re:Jni中C++和Java的参数传递2005-05-2308:37sundy 因为: 所以你将byte[]作为一个jbyteArray数组传递就可以了 #re:Jni中C++和Java的参数传递2005-09-2114:46小影 #re:Jni中C++和Java的参数传递2005-09-2117:47sundy 我没有直接传递过二维数组 但我想你可以把试一试二维数组转换成为一个Hashmap的数组传出来。 请参考"如何在Jni中传递出Hashmap的数组"的一些代码 #re:Jni中C++和Java的参数传递2005-12-2616:32wangjian #re:Jni中C++和Java的参数传递2005-12-2616:55wangjian #re:Jni中C++和Java的参数传递2005-12-2721:51sundy ...... 你看看设置的_Obj是不是都是同一个 #re:Jni中C++和Java的参数传递2005-12-2813:32wangjian 如下所示,我就是把你程序中(env)->SetShortField(_obj,ival,10)的参数10换成i,结果每个对象都是对象的serial成员值都是4,请问怎样实现多个不同对象的传递? (env)->SetShortField(_obj,ival,i); #re:Jni中C++和Java的参数传递2005-12-2815:15sundy 应该没有问题的呀, SetObjectArrayElement的时候,_obj是不同的吗 要不你将for循环改为: jstringjstr=WindowsTojstring(env,"我的磁盘名字是C:"); (env)->SetShortField(_obj,ival,0); (env)->SetObjectArrayElement(args,0,_obj); (env)->SetShortField(_obj,ival,1); (env)->SetObjectArrayElement(args,1,_obj); #re:Jni中C++和Java的参数传递2005-12-2920:42wangjian #re:Jni中C++和Java的参数传递2006-01-1711:07luli SQLRETURNSQLAllocHandle(SQLSMALLINTHandleType, SQLHANDLEInputHandle, SQLHANDLE*OutputHandlePtr 这是odbcapi里的一个函数SQLHANDLE是一个结构 c#里的引用方式如下 [DllImport("ODBC32.dll")] privatestaticexternshortSQLAllocHandle(shorthType,IntPtrinputHandle,outIntPtroutputHandle); 但我不清楚SQLHANDLE结构具体怎么构造的因此我无法用java类来模拟 #re:Jni中C++和Java的参数传递2006-01-1714:25luli 忘了补充SQLHANDLEInputHandle与SQLHANDLE*OutputHandlePtr 一个是结构一个是结构指针那我是否该如下模拟 classSQLHANDLE publicclasstest SQLHANDLEa=newSQLHANDLE(); publicstaticvoidmain(Stringargs[]){ inti=SQLAllocHandle(SQLSMALLINTHandleType,newSQLHANDLE(),a) #re:Jni中C++和Java的参数传递2006-03-2117:31Hefe WideCharToMultiByte(); MultiByteToWideChar(); 请问这两个函数实现什么功能,请作者给出代码,多谢! #re:Jni中C++和Java的参数传递2006-03-2208:47sundy #re:Jni中C++和Java的参数传递2006-03-2817:40dijk #re:Jni中C++和Java的参数传递2006-04-1621:33陈世雄 java中函数的处理中,对于对象类型(非基本类型int,long...)的输入参数,函数中是可以修改输入参数的内容,函数执行完毕,修改仍然是有效的。 jni中是否也是这样呢? #re:Jni中C++和Java的参数传递2006-04-1817:50王文波 你好: 向你请教一个问题:我想用jini来调用dll。我在jbuilder中新建的简单的project调用jini运行正常。但是,我现在要对一个工程软件进行二次开发,该软件的 开发也使用jbuilder生成一个project,然后放在指定的路径下就可以了,该软件在运行的时候会自动读取该project。我在这个软件二次开发的project中使用 jini,则总是报错:unsatisfiedlinkErrorget()。其中get()方法名。请问该怎么解决这个问题? #re:Jni中C++和Java的参数传递2006-05-2921:25single 这样作不对,不过我找到正确的方法了,要用构造函数生成新的对象。回复 --------------------------------------------------- #re:Jni中C++和Java的参数传递2006-08-2911:34yangyongfa 我正在做JNI,是在C++中调用JAVA类的方法,请问,我在JAVA类的方法中参数使用的是byte[],而我在C++中是把一个文件读成unsignedchar*,请问怎么可以正确调用JAVA中的方法类中方法原型:publicbooleanAddHoyuBox2DB(StringBoxName,byte[]BoxFile,byte[]WDHPic,intBoxFileBinLen,intWDHPicBinLen,StringParameterText,byte[]XXPic,intPicBinLen,byte[]SeriousPics,intSeriousPicsBinLen,StringFileLenStr) #re:Jni中C++和Java的参数传递2007-10-2515:27vampire #re:Jni中C++和Java的参数传递2007-12-1113:13Focus @single jobjectobjTemp=(env)->AllocObject(objectClass);//释放问题??这个是否需要释放不是很懂 //objectClass是函数上面给的那个 (env)->SetObjectField(objTemp,str,jstr); (env)->SetShortField(objTemp,ival,i); (env)->SetObjectArrayElement(args,i,objTemp); 这个可以实现数组元素相同的问题 近遇到一个问题,请各位帮忙解决下:如何将java传递过来的jbyteArray转换成C/C++中的BYTE数组?BYTE为unsignedchar类型这两个我理解应该是相同的吧,强制类型转换好像不启作用,应该如何转换呢? 该问题已经关闭:问题已解决,之前代码有问题jbyte*arrayBody=env->GetByteArrayElements(data,0);jsizetheArrayLengthJ=env->GetArrayLength(data);BYTE*starter=(BYTE*)arrayBody; 我之前为了给一个java项目添加IC卡读写功能,曾经查了很多资料发现查到的资料都是只说到第二步,所以剩下的就只好自己动手研究了.下面结合具体的代码来按这三个步骤分析. packagetest; publicclassLinkDll{//从指定地址读数据privatenativeStringreadData(inticdev,intoffset,intlen);publicStringreadData(inticdev,intoffset,intlen){returnthis.readDataTemp(icdev,offset,len);} static{System.loadLibrary("TestDll");//如果执行环境是linux这里加载的是SO文件,如果是windows环境这里加载的是dll文件}} 2>使用JDK的javah命令为这个类生成一个包含类中的方法定义的.h文件,可进入到class文件包的根目录下(只要是在classpath参数中的路径即可),使用javah命令的时候要加上包名javahtest.LinkDll,命令成功后生成一个名为test_LinkDll.h的头文件.文件内容如下: /*DONOTEDITTHISFILE-itismachinegenerated*/#include /*Headerforclasstest_LinkDll*/#ifndef_Included_test_LinkDll#define Included_test_LinkDll#ifdef__cplusplusextern"C"{#endif/**Class:test_LinkDll*Method:readDataTemp*Signature:(III)Ljava/lang/String;*/JNIEXPORTjstringJNICALLJava_test_LinkDll_readDataTemp(JNIEnv*,jobject,jint,jint,jint);#ifdef__cplusplus}#endif#endif 3>使用vc++6.0编写TestDll.dll文件,这个文件名是和java类中loadLibrary的名称一致.a>使用vc++6.0新建一个Win32Dynamic-LinkLibrary的工程文件,工程名指定为TestDllb>把源代码文件和头文件使用"AddFielstoProject"菜单加载到工程中,若使用c来编码,源码文件后缀名为.c,若使用c++来编码,源码文件扩展名为.cpp,这个一定要搞清楚,因为对于不同的语言,使用JNIEnv指针的方式是不同的.c>在这个文件里调用设备商提供的dll文件,设备商一般提供三种文件:dll/lib/h,这里假设分别为A.dll/A.lib/A.h.这个地方的调用分为动态调用和静态调用静态调用即是只要把被调用的dll文件放到path路径下,然后加载lib链接文件和.h头文件即可直接调用A.dll中的方法:把设备商提供的A.h文件使用"AddFielstoProject"菜单加载到这个工程中,同时在源代码文件中要把这个A.h文件使用include包含进来;然后依次点击"Project->settings"菜单,打开link选项卡,把A.lib添加到"Object/librarymodules"选项中.具体的代码如下://读出数据,需要注意的是如果是c程序在调用JNI函数时必须在JNIEnv的变量名前加*,如(*env)->xxx,如果是c++程序,则直接使用(env)->xxx #include //无符号字符指针,指向的内存空间用于存放读出的HEX形式的数据字符串unsignedchar*uncp_hex_passward=(unsignedchar*)malloc(i16_len);//无符号字符指针,指向的内存空间存放从HEX形式转换为ASC形式的数据字符串unsignedchar*uncp_asc_passward=(unsignedchar*)malloc(i16_len*2);//javachar指针,指向的内存空间存放从存放ASC形式数据字符串空间读出的数据字符串jchar*jcp_data=(jchar*)malloc(i16_len*2+1);//javaString,存放从javachar数组生成的String字符串,并返回给调用者jstringjs_data=0; //*********读出3个HEX形式的数据字符到uncp_hex_data指定的内存空间**********i16_result=readData(H_icdev,6,uncp_hex_data);//这里直接调用的是设备商提供的原型方法. if(i16_result!=0){printf("读卡错误......\n");//这个地方调用JNI定义的方法NewString(jchar*,jint),把jchar字符串转换为JString类型数据,返回到java程序中即是Stringreturn(env)->NewString(jca_result,3);} printf("读数据成功......\n"); //**************HEX形式的数据字符串转换为ASC形式的数据字符串**************i16_coverResult=hex_asc(uncp_hex_data,uncp_asc_data,3);if(i16_coverResult!=0){printf("字符转换错误!\n");return(env)->NewString(jca_result,3);} //**********ASCchar形式的数据字符串转换为jchar形式的数据字符串***********for(i_temp=0;i_temp //从指定地址读数据JNIEXPORTjstringJNICALLJava_readDataTemp(JNIEnv*env,jobjectjo,jintji_icdev,jintji_offset,jintji_len){inti_temp;inti_result;inti_icdev=(int)ji_icdev;inti_offset=(int)ji_offset;inti_len=(int)ji_len;jcharjca_result[5]={'e','r','r'};unsignedchar*uncp_data=(unsignedchar*)malloc(i_len);jchar*jcp_data=(jchar*)malloc(i_len);jstringjs_data=0;//HINSTANCE是win32中同HANDLE类似的一种数据类型,意为Handletoaninstance,常用来标记App实例,在这个地方首先把A.dll加载到内存空间,以一个App的形式存放,然后取 //使用win32的GetProcAddress方法取得A.dll中定义的名为readData的方法,并把这个方法转换为已被定义好的同结构的临时方法,//然后在下面的程序中,就可以使用这个临时方法了,使用这个临时方法在这时等同于使用A.dll中的原型方法.readData=(readDataTemp)GetProcAddress(dllhandle,"readData"); i_result=(*readData)(i_icdev,i_offset,i_len,uncp_data); if(i_result!=0){printf("读数据失败......\n");return(env)->NewString(jca_result,3);} for(i_temp=0;i_temp js_data=(env)->NewString(jcp_data,i_len); returnjs_data;} 4>以上即是一个java程序调用第三方dll文件的完整过程,当然,在整个过程的工作全部完成以后,就可以使用java类LinkDll中的publicStringradData(int,int,int)方法了,效果同直接使用c/c++调用这个设备商提供的A.dll文件中的readData方法几乎一样. 注:有一个名叫Jawin开源项目实现了直接读取第三方dll文件,不用自己辛苦去手写一个起传值转换作用的dll文件,有兴趣的可以研究一下.但是我用的时候不太顺手,有很多规则限制,像自己写程序时可以随意定义返回值,随意转换类型,用这个包的话这些都是不可能的了,所以我的项目还没开始就把它抛弃了. publicnativevoidprint();System.loadLibrary(“JNIExamples”);} 下面以实例1为例来详细说明编写jni方法的详细过程。(1)、定义包含jni函数的类Print.java: /************************************************************************theprint()functionwillcalltheprintf()funcionwhichisaANSIcfunciton**************************************************************************/ Publicnativevoidprint(); System.loadLibrary("JNIExamples"); /*DONOTEDITTHISFILE-itismachinegenerated*//*HeaderforclassPrint*/JNIEXPORTvoidJNICALLJava_Print_print(JNIEnv*,jobject);} 其中的加粗字体为要实现的JNI函数生命部分。(3)、编写JNI函数的实现部分Print.c JNIEXPORTvoidJNICALLJava_Print_print(JNIEnv*env,jobjectobj) printf("example1:inthisexampleaprintf()functioninANSICiscalled\n"); printf("Hello,theoutputisgeneratedbyprintf()functioninANSIC\n"); 在这个文件中实现了一个简单的Jni方法。该方法调用ANSIC中的printf()函数,输出了两个句子。 (4)、将本地函数编译到libJNIExamples.so的库中:使用语句:gcc-fPIC-I/usr/jdk1.5/include-I/usr/jdk1.5/include/linux-shared-olibJNIExamples.soPrint.c。(5)、至此Jni函数已全部实现,可以在java代码中调用拉。在此我们使用一个简单的类来对实现的jni方法进行测试,下面是PrintTest.java的源代码部分: publicstaticvoidmain(String[]args){Printp=newPrint();p.print();}} (6)、对PrintTest.java进行编译执行得到如下结果:example1:inthisexampleaprintf()functioninANSICiscalledHello,theoutputisgeneratedbyprintf()functioninANSIC.下面介绍的每个实例实现的步骤也都是按着上述步骤执行的。所以介绍时只介绍实现的关键部分。 (源程序为:java/Cfunction.javajava/C_functionTest.javasrc/Cfunction.csrc/Cfunction.h)当需要在java程序中调用用c所实现的函数是,需要在需要调用该c函数的类中定义一个jni方法,在该jni方法中去调用该c函数,相当于用java方法把c函数封装起来,以供java程序调用。在实例二中我们简单定义了一个printHello()函数,该函数的功能只是输出一句话,如果要在java程序中调用该函数,只需在jni函数中调用即可,和调用ANSIC中自带的prinf()函数没有任何区别。 (源程序为:java/CommonField.javajava/CommonFieldTest.javasrc/CommonField.csrc/CommonField.h)jni函数的实现部分是在c语言中实现的,如果它想访问java中定义的类对象的实例域需要作三步工作, (1)调用GetObjectClass()函数得到该对像的类,该函数返回一个jclass类型值。 (2)调用GetFieldID()函数得到要访问的实例域在该类中的id。(3)调用GetXXXField()来得到要访问的实例域的值。其中XXX和要访问的实例域的类型相对应。在jni中java编程语言和c语言数据类型的对应关系为java原始数据类型前加'j'表示对应c语言的数据类型例如boolean为jboolean,int为jint,double为jdouble等。对象类型的对应类型为jobject。在本实例中,您可以看到我们在java/CommonField.java中定义了类CommonField类,其中包含inta,intb两个实例域,我们要在jni函数getCommonField()中对这两个域进行访问和修改。你可以在src/CommonField.c中找到该函数的实现部分。以下语句是对该域的访问(以下代码摘自:src/CommonField.c): jclassclass_Field=(*env)->GetObjectClass(env,obj); jfieldIDfdA=(*env)->GetFieldID(env,class_Field,"a","I"); jfieldIDfdB=(*env)->GetFieldID(env,class_Field,"b","I"); jintvalueA=(*env)->GetIntField(env,obj,fdA); jintvalueB=(*env)->GetIntField(env,obj,fdB); 在jni中对所有jni函数的调用都要用到env指针,该指针也是每一个本地方法的第一个参数,他是函数指针表的指针,所以,必须在每一个jni调用前面加上(*env)->GetObjectClass(env,obj)函数调用返回obj对像的类型,其中obj参数表示要你想要得到类型的类对象。jfieldIDGetFieldID(JNIEnv*env,jclasscl,constcharname[],constcharsig[])该函数返回一个域的标识符name表示域名,sig表示编码的域签名。所谓编码的签名即编码类型的签名在上例中类中的a实例域为int型,用"I”来表示,同理"B”表示byte,"C”表示char,“D”表示double,”F”表示float,“J”表示long,“S”表示short,“V”表示void,”Z”表示boolean类型。GetIntField(env,obj,fdA),用来访问obj对象的fdA域,如果要访问的域为double类型,则要使用GetDoubleField(env,obj,fdA)来访问,即类型对应GetXXXField中的XXX。以下函数用来修改域的值: (*env)->SetIntField(env,obj,fdA,109);(*env)->SetIntField(env,obj,fdB,145); (java/Field.javajava/FieldTest.javasrc/Field.csrc/Field.h) 因为静态实例域并不属于某个对象,而是属于一个类,所以在要访问静态实例域时,和访问对象的实例域不同,它所调用的函数是(以实例四来说明,一下代码摘自src/Field.c): jclassclass_Field=(*env)->FindClass(env,"Field"); jfieldIDfdA=(*env)->GetStaticFieldID(env,class_Field,"a","I"); jintvalueA=(*env)->GetStaticIntField(env,class_Field,fdA); (*env)->SetStaticIntField(env,class_Field,fdA,111); 由于没有对象,必须使用FindClass代替GetObjectClass来获得类引用。在FindClass()的第二个参数是类的编码签名,类的编码签名和基本类型的编码签名有所不同,如果类在当前包中,就直接是类的名称,如果类不在当前包中则要加入该类的详细路径:例如String类在java.lang包中,则String的签名要写成(Ljava/lang/String;),其中的(L和;)是不可少的,其中(;)是表达是的终止符。其他三个函数和访问对象数据域基本没什么区别。 (java/CommonMethod.javajava/CommonMethodTest.javasrc/CommonMehod.csrc/CommonMethod.h)在jni函数中我们不仅要对java对象的数据域进行访问,而且有时也需要调用java中类对象已经实现的方法,实例五就是关于这方面的实现的。在src/CommonMethod.c中我们可以找到下面的代码: JNIEXPORTvoidJNICALLJava_CommonMethod_callMethod( JNIEnv*env,jobjectobj,jinta,jstrings) printf("example5:inthisexample,aobject'smethodwillbecalled\n"); jclassclass_CommonMethod=(*env)->GetObjectClass(env,obj); jmethodIDmd=(*env)->GetMethodID(env,class_CommonMethod,"print", "(ILjava/lang/String;)V"); (*env)->CallVoidMethod(env,obj,md,a,s); 该代码部分展示了如何实现对java类对象函数的调用过程。从以上代码部分我们可以看到,要实现该调用需要有三个步骤,调用三个函数 jmethodIDmd=(*env)->GetMethodID(env,class_CommonMethod,"print","(ILjava/lang/String;)V"); GetObjectClass(...)函数获得要调用对象的类;GetMethodID(...)获得要调用的方法相对于该类的ID号;CallXXXMethod(...)调用该方法。 (java/Method.javajava/MethodTest.javasrc/Method.hsrc/Method.c)实例五中介绍了如何调用类对象的方法,在此实例中我们将介绍如何调用java类的静态方法在此实例中我们在/java/Method.java中定义了静态方法:publicstaticvoidprint(){ System.out.println("thisisastaticmethodofclassMethod");} 该函数的功能就是打印字符串“thisisastaticmethodofclassMethod”;我们在src/Method.c中实现了对该方法调用的jni函数: JNIEXPORTvoidJNICALLJava_Method_callMethod(JNIEnv*env,jobjectobj) printf("example6:inthisexample,theclass'sstaticmethodwillbecalled\n"); jclassclass_Method=(*env)->FindClass(env,"Method"); jmethodIDmd=(*env)->GetStaticMethodID(env,class_Method,"print","()V"); (*env)->CallStaticVoidMethod(env,class_Method,md);} 和实例五不同的是,我们要调用的三个函数变为:FindClass(...)、GetStaticMethodID(...)、CallStaticVoidMethod(...)。其中的机制和实例五是一样的。再次就不做过多的介绍。 (java/Basic.javajava/BasicTest.javasrc/Basic.csrc/Basic.h)在java/Basic.java中,我们定义了一个publicnativevoidraiseValue(inta)函数,该函数将打印使value的值增加a,并打印原来的value和新的value值。在src/Basic.c中给出了该jni函数的实现部分。 JNIEXPORTvoidJNICALLJava_Basic_raiseValue( JNIEnv*env,jobjectobj,jinta) printf("example7:inthisexample,aintegertypeparamentwillbepassedtothejnimethod\n"); jclassclass_Basic=(*env)->GetObjectClass(env,obj); jfieldIDfd=(*env)->GetFieldID(env,class_Basic,"value","I"); jintv=(*env)->GetIntField(env,obj,fd); v=v+a; (*env)->SetIntField(env,obj,fd,v); 在此函数实现中,因为要访问Basic类中的value域,所以调用了GetObjectClass(...),GetFieldID(...),GetIntField(...)函数获取value值,下面一步的“=v+a;”说明,传递基本类型参数的处理方式和在c语言中的基本数据类型的处理无异。 (java/Book.javajava/BookTest.javasrc/BookTest.csrc/BookTest.h)在该实例中演示了在jni函数中传递对象函数的过程。我们在该实例中定义了一个类Book total_page=t; publicintgetTotalPage(){} publicintgetCurrentPage(){} current_page++; 然后我们在java/BookTest.java中定义jni函数 publicnativevoidbookCurrentStatus(Bookb); 该函数需要一个Book类型的参数,并返回该参数的当前状态,包括该书一共有多少页的total_page,以及当前页current_page。函数的实现部分为(src/BookTest.c) JNIEXPORTvoidJNICALLJava_BookTest_bookCurrentStatus(JNIEnv*env, jobjectthis_obj,jobjectobj) printf("example8:inthisexample,aobjectparamentwillbepassedtothejnimethod。\n"); jclassclass_book=(*env)->GetObjectClass(env,obj); jmethodIDid_getTotal=(*env)->GetMethodID(env, class_book,"getTotalPage","()I"); jmethodIDid_getCurrent=(*env)->GetMethodID(env, class_book,"getCurrentPage","()I"); jinttotal_page=(*env)->CallIntMethod(env, obj,id_getTotal); jintcurrent_page=(*env)->CallIntMethod(env, obj,id_getCurrent); printf("thetotalpageis:%dandthecurrentpageis:%d\n", total_page,current_page); 该函数包含三个参数(JNIEnv*env,jobjectthis_obj,jobjectobj),第二个jobjectthis_obj参数表示当前的jni函数所属于的类对象,第三个jobjectobj参数表示传递的参数Book类型的类对象。 对于实现部分,基本和实例五--调用java类对象的方法中的操作相同,就不作详解。 (java/Str.javajava/StrTest.javasrc/Str.csrc/Str.h)在该实例中我们讲解如何传递、处理字符串参数。在java/Str.java中我们定义了一个printString(Strings)的方法,用来处理字符串参数。在src/Str.c中我们可以看到该函数的实现部分: JNIEXPORTvoidJNICALLJava_Str_printString(JNIEnv*env, jobjectobj,jstrings) printf("example9:inthisexample,aStringobjectparamentwillbepassedtothejnimethod.\n"); constchar*string=(char*)(*env)->GetStringUTFChars(env,s,NULL); printf("%sisputoutinnativemethod\n",string); (*env)->ReleaseStringUTFChars(env,s,(jbyte*)string); 实现过程中调用了两个函数:GetStringUTFChars(...)、ReleaseStringUTFChars(...)。GetStringUTFChars(...)用来获取String对象的字符串,并将其抓那还为char*类型,这应该字符串就可以在c语言中进行处理拉。ReleaseStringUTFChars(...)用于当该字符串使用完成后,将其进行垃圾回收。记住,当使用完字符串时一定不要忘记调用该函数。 (java/Arr.javajava/ArrTest.javasrc/Arr.csrc/Arr.h) java中所有的数组类型都有相对应的c语言类型,其中jarray类型表示一个泛型数组 boolean[]--jbooleanArray byte[]--jbyteArray char[]--jcharArary int[]---jcharArray short[]---jshortArray long[]---jlongArray float[]--jfloatArray double[]—-jdoubleArray Object[]---jobjectArray。 当访问数组时,可以通过GetObjectAraryElement和SetObjectArrayElement方法访问对象数组的元素。 而对于一般类型数组,你可以调用GetXXXAraryElements来获取一个只想数组起始元素的指针,而当你不在使用该数组时,要记得调用ReleaseXXXArrayElements,这样你所作的改变才能保证在原始数组里得到反映。当然如果你需要得到数组的长度,可以调用GetArrayLength函数。 在本实例中,我们在Arr.java中定义一个本地方法:print(intintArry[]),该函数的功能为对该数组进行输出,在src/Arr.c中我们可以看到该方法的实现过程如下: JNIEXPORTvoidJNICALLJava_Arr_print(JNIEnv*env, jobjectobj,jintArrayintArray) printf("example10:inthisexample,aarrayparamentwillbepassedtothejnimethod.\n"); jint*arr=(*env)->GetIntArrayElements(env,intArray,NULL); n=(*env)->GetArrayLength(env,intArray); printf("thenativemethodoutputtheintarray\n"); for(i=0;i<(*env)->GetArrayLength(env,intArray);i++) printf("%d",arr[i]); (*env)->ReleaseIntArrayElements(env,intArray,arr,0); 我们在此调用了GetIntArrayElements(...)来获取一个指向intArray[]数组第一个元素的指针。用getArrayLength(..)函数来得到数组的长度,以方便数组遍历时使用。最后应用ReleaseArrayElements(...)函数来释放该数组指针。 (java/ReturnValue.javajava/ReturnValueTest.javajava/BookClass.javasrc/ReturnValue.csrc/ReturnValue.h) 在java/ReturnValue类中定义了三个jni方法:returnInt(),returnString(),returnObject()三个方法,分别返回int,String,Object类型的值。 其在src/ReturnValue.c中的实现分别为: JNIEXPORTjintJNICALLJava_ReturnValue_returnInt( JNIEnv*env,jobjectobj) jclassclass_ReturnValue=(*env)->GetObjectClass(env,obj); jfieldIDfd=(*env)->GetFieldID(env,class_ReturnValue,"value","I"); returnv; *Signature:()Ljava/lang/String; JNIEXPORTjstringJNICALLJava_ReturnValue_returnString( printf("example11:inthisexample,theintandobjectofreturnvaluewillbeproceeding\n"); jfieldIDfd=(*env)->GetFieldID(env,class_ReturnValue,"name","Ljava/lang/String;"); jstringjstr=(jstring)(*env)->GetObjectField(env,obj,fd); **Method:returnObject JNIEXPORTjobjectJNICALLJava_ReturnValue_returnObject( jfieldIDfd=(*env)->GetFieldID(env,class_ReturnValue,"myBook","LBookClass;"); jobjectjbook=(jstring)(*env)->GetObjectField(env,obj,fd); 在这里分别涉及到了对java类对象的一般参数,String参数,以及Object参数的访问。 (java/Test.javasrc/CreateObj.csrc/CreateObj.h) 如果想要在jni函数创建java类对象则要引用java类的构造器方法,通过调用NewObject函数来实现。 NewObject函数的调用方式为: jobjectobj_new=(*env)->NewObject(env,class,methodid,paraments);在该实例中,我们在java/Test.java中定义了Book1类,要在CreateObj类的modifyProperty()jni方法中创建该类对象。我们可以在src/CreateObj.c中看到该jni方法创建对象的过程: jobjectbook; jclassclass_book; jmethodIDmd_book; class_book=(*env)->FindClass(env,"LBook1;"); md_book=(*env)->GetMethodID(env,class_book," book=(*env)->NewObject(env,class_book,md_book,100,1,"huanghe"); 在创建对象的过程中可以看到,要创建一个java类对象,首先需要得到得到使用FindClass函数得到该类,然后使用GetMethodID方法得到该类的构造器方法id,主义在此时构造器的函数名始终为:"”,其后函数的签名要符合函数签名规则。在此我们的构造器有三个参数:int,int,String. 并且其返回值类型要永久为空,所以函数签名为:"(IILjava/lang/String;)V" 然后我们调用NewObject()函数来创建该类的对象,在此之后就可以使用该对象拉。 例如:(*env)->NewObject(env,class_book,md_book,100,1,”huanghe”); 修改为:(env)->NewObject(class_book,md_book,100,1,”huanghe”); 即修改(*env)为(env)再把参数中的env去掉。然后把所有c的函数改为c++的函数就OK拉。 具体情况可以去查看我们的c++实例代码. 级别:初级 1999年5月01日 本文为在32位Windows平台上实现Java本地方法提供了实用的示例、步骤和准则。这些示例包括传递和返回常用的数据类型。 简介 本文提供调用本地C代码的Java代码示例,包括传递和返回某些常用的数据类型。本地方法包含在特定于平台的可执行文件中。就本文中的示例而言,本地方法包含在Windows32位动态链接库(DLL)中。 不过我要提醒您,对Java外部的调用通常不能移植到其他平台上,在applet中还可能引发安全异常。实现本地代码将使您的Java应用程序无法通过100%纯Java测试。但是,如果必须执行本地调用,则要考虑几个准则: ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 在第一个示例中,我们将三个常用参数类型传递给本地函数:String、int和boolean。本例说明在本地C代码中如何引用这些参数。 publicclassMyNative publicvoidshowParms(Strings,inti,booleanb) showParms0(s,i,b); privatenativevoidshowParms0(Strings,inti,booleanb); static System.loadLibrary("MyNative"); 下一步是生成C代码来实现showParms0方法。此方法的C函数原型是通过对.class文件使用javah实用程序来创建的,而.class文件是通过编译MyNative.java文件生成的。这个实用程序可在JDK中找到。下面是javah的用法: javacMyNative.java(将.java编译为.class) javah-jniMyNative(生成.h文件) 这将生成一个MyNative.h文件,其中包含一个本地方法原型,如下所示: *Class:MyNative *Method:showParms0 *Signature:(Ljava/lang/String;IZ)V JNIEXPORTvoidJNICALLJava_MyNative_showParms0 (JNIEnv*,jobject,jstring,jint,jboolean); 本地方法是在文件MyNative.c中用C语言实现的: #include"MyNative.h" constchar*szStr=(*env)->GetStringUTFChars(env,s,0); (*env)->ReleaseStringUTFChars(env,s,szStr); JNIAPI,GetStringUTFChars,用来根据Java字符串或jstring参数创建C字符串。这是必需的,因为在本地代码中不能直接读取Java字符串,而必须将其转换为C字符串或Unicode。有关转换Java字符串的详细信息,请参阅标题为NLSStringsandJNI的一篇论文。但是,jboolean和jint值可以直接使用。 MyNative.dll是通过编译C源文件创建的。下面的编译语句使用MicrosoftVisualC++编译器: cl-Ic:\jdk1.1.6\include-Ic:\jdk1.1.6\include\win32-LDMyNative.c -FeMyNative.dll 其中c:\jdk1.1.6是JDK的安装路径。 MyNative.dll已创建好,现在就可将其用于MyNative类了。可以这样测试这个本地方法:在MyNative类中创建一个main方法来调用showParms方法,如下所示: MyNativeobj=newMyNative(); obj.showParms("Hello",23,true); obj.showParms("World",34,false); 当运行这个Java应用程序时,请确保MyNative.dll位于Windows的PATH环境变量所指定的路径中或当前目录下。当执行此Java程序时,如果未找到这个DLL,您可能会看到以下的消息: javaMyNative Can'tfindclassMyNative 这是因为static子句无法加载这个DLL,所以在初始化MyNative类时引发异常。Java解释器处理这个异常,并报告一个一般错误,指出找不到这个类。如果用-verbose命令行选项运行解释器,您将看到它因找不到这个DLL而加载UnsatisfiedLinkError异常。 如果此Java程序完成运行,就会输出以下内容: String=[Hello] int=23 boolean=true String=[World] int =34 本例将说明如何在本地方法中实现返回代码。将这个方法添加到MyNative类中,这个类现在变为以下形式: publicinthypotenuse(inta,intb) returnhyptenuse0(a,b); privatenativeinthypotenuse0(inta,intb); /*测试本地方法*/ System.out.println(obj.hypotenuse(3,4)); System.out.println(obj.hypotenuse(9,12)); 公用的hypotenuse方法调用本地方法hypotenuse0来根据传递的参数计算值,并将结果作为一个整数返回。这个新本地方法的原型是使用javah生成的。请注意,每次运行这个实用程序时,它将自动覆盖当前目录中的MyNative.h。按以下方式执行javah: javah-jniMyNative 生成的MyNative.h现在包含hypotenuse0原型,如下所示: *Method:hypotenuse0 *Signature:(II)I JNIEXPORTjintJNICALLJava_MyNative_hypotenuse0 (JNIEnv*,jobject,jint,jint); 该方法是在MyNative.c源文件中实现的,如下所示: (JNIEnv*env,jobjectobj,jinta,jintb) intrtn=(int)sqrt((double)((a*a)+(b*b))); 再次请注意,jint和int值是可互换的。使用相同的编译语句重新编译这个DLL: 现在执行javaMyNative将输出5和15作为斜边的值。 您可能在上面的示例中已经注意到,实例化的MyNative对象是没必要的。实用方法通常不需要实际的对象,通常都将它们创建为静态方法。本例说明如何用一个静态方法实现上面的示例。更改MyNative.java中的方法签名,以使它们成为静态方法: publicstaticinthypotenuse(inta,intb) returnhypotenuse0(a,b); ... privatestaticnativeinthypotenuse0(inta,intb); 现在运行javah为hypotenuse0创建一个新原型,生成的原型如下所示: (JNIEnv*,jclass,jint,jint); C源代码中的方法签名变了,但代码还保持原样: (JNIEnv*env,jclasscls,jinta,jintb) 本质上,jobject参数已变为jclass参数。此参数是指向MyNative.class的一个句柄。main方法可更改为以下形式: System.out.println(MyNative.hypotenuse(3,4)); System.out.println(MyNative.hypotenuse(9,12)); 因为方法是静态的,所以调用它不需要实例化MyNative对象。本文后面的示例将使用静态方法。 本例说明如何传递数组型参数。本例使用一个基本类型,boolean,并将更改数组元素。下一个示例将访问String(非基本类型)数组。将下面的方法添加到MyNative.java源代码中: publicstaticvoidsetArray(boolean[]ba) for(inti=0;i ba[i]=true; setArray0(ba); privatestaticnativevoidsetArray0(boolean[]ba); 在本例中,布尔型数组被初始化为true,本地方法将把特定的元素设置为false。同时,在Java源代码中,我们可以更改main以使其包含测试代码: boolean[]ba=newboolean[5]; MyNative.setArray(ba); System.out.println(ba[i]); 在编译源代码并执行javah以后,MyNative.h头文件包含以下的原型: *Method:setArray0 *Signature:([Z)V JNIEXPORTvoidJNICALLJava_MyNative_setArray0 (JNIEnv*,jclass,jbooleanArray); 请注意,布尔型数组是作为单个名为jbooleanArray的类型创建的。基本类型有它们自已的数组类型,如jintArray和jcharArray。非基本类型的数组使用jobjectArray类型。下一个示例中包括一个jobjectArray。这个布尔数组的数组元素是通过JNI方法GetBooleanArrayElements来访问的。针对每种基本类型都有等价的方法。这个本地方法是如下实现的: (JNIEnv*env,jclasscls,jbooleanArrayba) jboolean*pba=(*env)->GetBooleanArrayElements(env,ba,0); jsizelen=(*env)->GetArrayLength(env,ba); //更改偶数数组元素 (*env)->ReleaseBooleanArrayElements(env,ba,pba,0); 本例将通过最常用的非基本类型,JavaString,说明如何访问非基本对象的数组。字符串数组被传递给本地方法,而本地方法只是将它们显示到控制台上。MyNative类定义中添加了以下几个方法: publicstaticvoidshowStrings(String[]sa) showStrings0(sa); privatestaticvoidshowStrings0(String[]sa); 并在main方法中添加了两行进行测试: String[]sa=newString[]{"Hello,","world!","JNI","is","fun."}; MyNative.showStrings(sa); 本地方法分别访问每个元素,其实现如下所示。 JNIEXPORTvoidJNICALLJava_MyNative_showStrings0 (JNIEnv*env,jclasscls,jobjectArraysa) intlen=(*env)->GetArrayLength(env,sa); jobjectobj=(*env)->GetObjectArrayElement(env,sa,i); jstringstr=(jstring)obj; constchar*szStr=(*env)->GetStringUTFChars(env,str,0); printf("%s",szStr); (*env)->ReleaseStringUTFChars(env,str,szStr); printf("\n"); 数组元素可以通过GetObjectArrayElement访问。 在本例中,我们知道返回值是jstring类型,所以可以安全地将它从jobject类型转换为jstring类型。字符串是通过前面讨论过的方法打印的。有关在Windows中处理Java字符串的信息,请参阅标题为NLSStringsandJNI的一篇论文。 最后一个示例说明如何在本地代码中创建一个字符串数组并将它返回给Java调用者。MyNative.java中添加了以下几个方法: publicstaticString[]getStrings() returngetStrings0(); privatestaticnativeString[]getStrings0(); 更改main以使showStrings将getStrings的输出显示出来: MyNative.showStrings(MyNative.getStrings()); 实现的本地方法返回五个字符串。 JNIEXPORTjobjectArrayJNICALLJava_MyNative_getStrings0 (JNIEnv*env,jclasscls) args=(*env)->NewObjectArray(env,len,(*env)->FindClass(env,"java/lang/String"),0); str=(*env)->NewStringUTF(env,sa[i]); (*env)->SetObjectArrayElement(env,args,i,str); 字符串数组是通过调用NewObjectArray创建的,同时传递了String类和数组长度两个参数。JavaString是使用NewStringUTF创建的。String元素是使用SetObjectArrayElement存入数组中的。 现在您已经为您的应用程序创建了一个本地DLL,但在调试时还要牢记以下几点。如果使用Java调试器java_g.exe,则还需要创建DLL的一个“调试”版本。这只是表示必须创建同名但带有一个_g后缀的DLL版本。就MyNative.dll而言,使用java_g.exe要求在Windows的PATH环境指定的路径中有一个MyNative_g.dll文件。在大多数情况下,这个DLL可以通过将原文件重命名或复制为其名称带缀_g的文件。 现在,Java调试器不允许您进入本地代码,但您可以在Java环境外使用C调试器(如MicrosoftVisualC++)调试本地方法。首先将源文件导入一个项目中。将编译设置调整为在编译时将include目录包括在内: c:\jdk1.1.6\include;c:\jdk1.1.6\include\win32 将配置设置为以调试模式编译DLL。在ProjectSettings中的Debug下,将可执行文件设置为java.exe(或者java_g.exe,但要确保您生成了一个_g.dll文件)。程序参数包括包含main的类名。如果在DLL中设置了断点,则当调用本地方法时,执行将在适当的地方停止。 下面是设置一个VisualC++6.0项目来调试本地方法的步骤。 当执行这个程序时,忽略“在java.exe中找不到任何调试信息”的消息。当调用本地方法时,在C代码中设置的任何断点将在适当的地方停止Java程序的执行。 JNI方法和C++ 上面这些示例说明了如何在C源文件中使用JNI方法。如果使用C++,则请将相应方法的格式从: (*env)->JNIMethod(env,....); 更改为: env->JNIMethod(...); 在C++中,JNI函数被看作是JNIEnv类的成员方法。 字符串和国家语言支持 本文中使用的技术用UTF方法来转换字符串。使用这些方法只是为了方便起见,如果应用程序需要国家语言支持(NLS),则不能使用这些方法。有关在Windows和NLS环境中处理Java字符串正确方法,请参标题为NLSStringsandJNI的一篇论文。 4.小结 本文提供的示例用最常用的数据类据(如jint和jstring)说明了如何实现本地方法,并讨论了Windows特定的几个问题,如显示字符串。本文提供的示例并未包括全部JNI,JNI还包括其他参数类型,如jfloat、jdouble、jshort、jbyte和jfieldID,以及用来处理这些类型的方法。有关这个主题的详细信息,请参阅SunMicrosystems提供的Java本地接口规范。 5.关于作者:DavidWendt是IBMWebSphereStudio的一名程序员,该工作室位于北卡罗莱纳州的ResearchTrianglePark。可以通过wendt@us.ibm.com与他联系。 字号:大中小 JNI编程系列之基础篇 最近干一个活需要从Java调用C++编译的动态链接库,研究了一下JNI,现在将网上搜罗的文档和自己的体会贡献出来。 JNI的做法是:通过在方法前加上关键字native来识别本地方法,然后用本地语言(如C,C++)来实现该方法,并编译成动态链接库,在Java的类中调用该动态链接库,然后就可以像使用Java自己的方法一样使用native方法了。这样做的好处是既具有了Java语言的便利性,又具有了C语言的效率;另一个好处是可以利用已有的C代码,避免重复开发。 下面从最简单的JNI程序入手,介绍如何进行JNI编程。 下面是一个简单的Java程序HelloWorld.java, privatenativevoidprint(); newHelloWorld().print(); System.loadLibrary("HelloWorld"); 在这个例子中,注意到两个关键的地方。 首先是第二行 第二个地方是 这行代码的作用是调用名为HelloWorld的动态链接库,在Windows下,是HelloWorld.dll,在Linux下是HelloWorld.so。 显然现在这个Java程序是不能运行的。要运行它先要做下面的工作。执行 >javacHelloWorld.java >javah-jniHelloWorld 执行完这两条语句之后,会生成一个名为HelloWorld.h的文件,它的内容应该是这样的, #ifndef_Included_HelloWorld #define_Included_HelloWorld *Method:print JNIEXPORTvoidJNICALLJava_HelloWorld_print(JNIEnv*,jobject); 注意到在这个程序的开头有这样一行代码, 这里的jni.h,只要你安装了JDK就能在安装目录下找到它。 不要修改这个文件的内容,现在要做的是写一个名为HelloWorld.cpp程序,实现上面这个.h文件里的函数, //------------ #include"HelloWorld.h" #include JNIEXPORTvoidJNICALLJava_HelloWorld_print(JNIEnv*,jobject){ std::cout<<"HelloWorld!"< //-------------- 这是一个最简单的C++程序。将它编译为动态链接库,我们得到HelloWorld.dll,将这个.dll文件拷到HelloWorld.java文件所在的目录下。执行 >javaHelloWorld 你会看到屏幕上输出 >HelloWorld! 现在来总结一下,要实现JNI编程,需要以下几个步骤: 1.写一个Java程序,将你希望用C语言实现的方法用native关键字标识出来,同时加上调用动态链接库的语句。 2.执行下面两条语句,生成.h文件 在class或bin目录下(其下或其子目录下有javac命令生成的*.class文件)执行 3.根据.h文件,写一个.cpp程序,编译成动态链接库,并将其复制到.java文件所在的路径下。 4.执行javaHelloWorld 本篇将介绍在JNI编程中如何传递参数和返回值。 首先要强调的是,native方法不但可以传递Java的基本类型做参数,还可以传递更复杂的类型,比如String,数组,甚至自定义的类。这一切都可以在jni.h中找到答案。 1.Java基本类型的传递 用过Java的人都知道,Java中的基本类型包括boolean,byte,char,short,int,long,float,double这样几种,如果你用这几种类型做native方法的参数,当你通过javah-jni生成.h文件的时候,只要看一下生成的.h文件,就会一清二楚,这些类型分别对应的类型是jboolean,jbyte,jchar,jshort,jint,jlong,jfloat,jdouble。这几种类型几乎都可以当成对应的C++类型来用,所以没什么好说的。 2.String参数的传递 Java的String和C++的string是不能对等起来的,所以处理起来比较麻烦。先看一个例子, //***************** classPrompt{ //nativemethodthatprintsapromptandreadsaline privatenativeStringgetLine(Stringprompt); Promptp=newPrompt(); Stringinput=p.getLine("Typealine:"); System.out.println("Usertyped:"+input); System.loadLibrary("Prompt"); 在这个例子中,我们要实现一个native方法 StringgetLine(Stringprompt); 读入一个String参数,返回一个String值。 通过执行javah-jni得到的头文件是这样的 #ifndef_Included_Prompt #define_Included_Prompt JNIEXPORTjstringJNICALLJava_Prompt_getLine(JNIEnv*env,jobjectthis,jstringprompt); jstring是JNI中对应于String的类型,但是和基本类型不同的是,jstring不能直接当作C++的string用。如果你用 cout< 编译器肯定会扔给你一个错误信息的。 其实要处理jstring有很多种方式,这里只讲一种我认为最简单的方式,看下面这个例子, #include"Prompt.h" JNIEXPORTjstringJNICALLJava_Prompt_getLine(JNIEnv*env,jobjectobj,jstringprompt) constchar*str; str=env->GetStringUTFChars(prompt,false); if(str==NULL){ returnNULL;/*OutOfMemoryErroralreadythrown*/ std::cout< env->ReleaseStringUTFChars(prompt,str); //returnastring char*tmpstr="returnstringsucceeded"; jstringrtstr=env->NewStringUTF(tmpstr); returnrtstr; 在上面的例子中,作为参数的prompt不能直接被C++程序使用,先做了如下转换 将jstring类型变成一个char*类型。 返回的时候,要生成一个jstring类型的对象,也必须通过如下命令, 这里用到的GetStringUTFChars和NewStringUTF都是JNI提供的处理String类型的函数,还有其他的函数这里就不一一列举了。 /****************************************************/ JNI编程系列之中级篇(下) 和String一样,JNI为Java基本类型的数组提供了j*Array类型,比如int[]对应的就是jintArray。来看一个传递int数组的例子,Java程序就不写了, //******************* JNIEXPORTjintJNICALLJava_IntArray_sumArray(JNIEnv*env,jobjectobj,jintArrayarr) jint*carr; carr=env->GetIntArrayElements(arr,false); if(carr==NULL){ return0;/*exceptionoccurred*/ jintsum=0; for(inti=0;i<10;i++){ sum+=carr[i]; env->ReleaseIntArrayElements(arr,carr,0); 这个例子中的GetIntArrayElements和ReleaseIntArrayElements函数就是JNI提供用于处理int数组的函数。如果试图用arr[i]的方式去访问jintArray类型,毫无疑问会出错。JNI还提供了另一对函数GetIntArrayRegion和ReleaseIntArrayRegion访问int数组,就不介绍了,对于其他基本类型的数组,方法类似。 在JNI中,二维数组和String数组都被视为object数组,因为数组和String被视为object。仍然用一个例子来说明,这次是一个二维int数组,作为返回值。 JNIEXPORTjobjectArrayJNICALLJava_ObjectArrayTest_initInt2DArray(JNIEnv*env,jclasscls,intsize) jobjectArrayresult; jclassintArrCls=env->FindClass("[I"); result=env->NewObjectArray(size,intArrCls,NULL); for(inti=0;i jinttmp[256];/*makesureitislargeenough!*/ jintArrayiarr=env->NewIntArray(size); for(intj=0;j tmp[j]=i+j; env->SetIntArrayRegion(iarr,0,size,tmp); env->SetObjectArrayElement(result,i,iarr); env->DeleteLocalRef(iarr); returnresult; 上面代码中的第三行, 因为要返回值,所以需要新建一个jobjectArray对象。 是创建一个jclass的引用,因为result的元素是一维int数组的引用,所以intArrCls必须是一维int数组的引用,这一点是如何保证的呢?注意FindClass的参数"[I",JNI就是通过它来确定引用的类型的,I表示是int类型,[标识是数组。对于其他的类型,都有相应的表示方法, Zboolean Bbyte Cchar Sshort Iint Jlong Ffloat Ddouble String是通过“Ljava/lang/String;”表示的,那相应的,String数组就应该是“[Ljava/lang/String;”。 还是回到代码, 的作用是为result分配空间。 是为一维int数组iarr分配空间。 是为iarr赋值。 是为result的第i个元素赋值。 通过上面这些步骤,我们就创建了一个二维int数组,并赋值完毕,这样就可以做为参数返回了。 如果了解了上面介绍的这些内容,基本上大部分的任务都可以对付了。虽然在操作数组类型,尤其是二维数组和String数组的时候,比起在单独的语言中编程要麻烦,但既然我们享受了跨语言编程的好处,必然要付出一定的代价。 有一点要补充的是,本文所用到的函数调用方式都是针对C++的,如果要在C中使用,所有的env->都要被替换成(*env)->,而且后面的函数中需要增加一个参数env,具体请看一下jni.h的代码。另外还有些省略的内容,可以参考JNI的文档:JavaNativeInterface6.0Specification,在JDK的文档里就可以找到。如果要进行更深入的JNI编程,需要仔细阅读这个文档。接下来的高级篇,也会讨论更深入的话题。 在本篇中,将会涉及关于JNI编程更深入的话题,包括:在native方法中访问Java类的域和方法,将Java中自定义的类作为参数和返回值传递等等。了解这些内容,将会对JNI编程有更深入的理解,写出的程序也更清晰,易用性更好。 在前两篇的例子中,都是将native方法放在main方法的Java类中,实际上,完全可以在任何类中定义native方法。这样,对于外部来说,这个类和其他的Java类没有任何区别。 native方法虽然是native的,但毕竟是方法,那么就应该同其他方法一样,能够访问类的私有域和方法。实际上,JNI的确可以做到这一点,我们通过几个例子来说明, publicclassClassA{ Stringstr_="abcde"; intnumber_; publicnativevoidnativeMethod(); privatevoidjavaMethod(){ System.out.println("calljavamethodsucceeded"); System.loadLibrary("ClassA"); 在这个例子中,我们在一个没有main方法的Java类中定义了native方法。我们将演示如何在nativeMethod()中访问域str_,number_和方法javaMethod(),nativeMethod()的C++实现如下, JNIEXPORTvoidJNICALLJava_testclass_ClassCallDLL_nativeMethod(JNIEnv*env,jobjectobj) //accessfield jclasscls=env->GetObjectClass(obj); jfieldIDfid=env->GetFieldID(cls,"str_","Ljava/lang/String;"); jstringjstr=(jstring)env->GetObjectField(obj,fid); constchar*str=env->GetStringUTFChars(jstr,false); if(std::string(str)=="abcde") std::cout<<"accessfieldsucceeded"< jinti=2468; fid=env->GetFieldID(cls,"number_","I"); env->SetIntField(obj,fid,i); //accessmethod jmethodIDmid=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表示返回值为空,()表示参数为空。如果是更复杂的函数类型如何表示?看一个例子, longf(intn,Strings,int[]arr); 这个函数的类型符号是(ILjava/lang/String;[I)J,I表示int类型,Ljava/lang/String;表示String类型,[I表示int数组,J表示long。这些都可以在文档中查到。 JNI不仅能使用Java的基础类型,还能使用用户定义的类,这样灵活性就大多了。大体上使用自定义的类和使用Java的基础类(比如String)没有太大的区别,关键的一点是,如果要使用自定义类,首先要能访问类的构造函数,看下面这一段代码,我们在native方法中使用了自定义的Java类ClassB, jclasscls=env->FindClass("Ltestclass/ClassB;"); jmethodIDid=env->GetMethodID(cls," jdoubledd=0.033; jvalueargs[1]; args[0].d=dd; jobjectobj=env->NewObjectA(cls,id,args); 首先要创建一个自定义类的引用,通过FindClass函数来完成,参数同前面介绍的创建String对象的引用类似,只不过类名称变成自定义类的名称。然后通过GetMethodID函数获得这个类的构造函数,注意这里方法的名称是" 生成了一个ClassB的对象,args是ClassB的构造函数的参数,它是一个jvalue*类型。 通过以上介绍的三部分内容,native方法已经看起来完全像Java自己的方法了,至少主要功能上齐备了,只是实现上稍麻烦。而了解了这些,JNI编程的水平也更上一层楼。下面要讨论的话题也是一个重要内容,至少如果没有它,我们的程序只能停留在演示阶段,不具有实用价值。 在C++和Java的编程中,异常处理都是一个重要的内容。但是在JNI中,麻烦就来了,native方法是通过C++实现的,如果在native方法中发生了异常,如何传导到Java呢? JNI提供了实现这种功能的机制。我们可以通过下面这段代码抛出一个Java可以接收的异常, jclasserrCls; env->ExceptionDescribe(); env->ExceptionClear(); errCls=env->FindClass("java/lang/IllegalArgumentException"); env->ThrowNew(errCls,"thrownfromC++code"); 如果要抛出其他类型的异常,替换掉FindClass的参数即可。这样,在Java中就可以接收到native方法中抛出的异常。 Java跨平台的特性使Java越来越受开发人员的欢迎,但也往往会听到不少的抱怨:用Java开发的图形用户窗口界面每次在启动的时候都会跳出一个控制台窗口,这个控制台窗口让本来非常棒的界面失色不少。怎么能够让通过Java开发的GUI程序不弹出Java的控制台窗口呢?其实现在很多流行的开发环境例如JBuilder、Eclipse都是使用纯Java开发的集成环境。这些集成环境启动的时候并不会打开一个命令窗口,因为它使用了JNI(JavaNativeInterface)的技术。通过这种技术,开发人员不一定要用命令行来启动Java程序,可以通过编写一个本地GUI程序直接启动Java程序,这样就可避免另外打开一个命令窗口,让开发的Java程序更加专业。 JNI允许运行在虚拟机的Java程序能够与其它语言(例如C和C++)编写的程序或者类库进行相互间的调用。同时JNI提供的一整套的API,允许将Java虚拟机直接嵌入到本地的应用程序中。图1是Sun站点上对JNI的基本结构的描述。 图1JNI基本结构描述图 本文将介绍如何在C/C++中调用Java方法,并结合可能涉及到的问题介绍整个开发的步骤及可能遇到的难题和解决方法。本文所采用的工具是Sun公司创建的JavaDevelopmentKit(JDK)版本1.3.1,以及微软公司的VisualC++6开发环境。 图2设置集成开发环境图 将目录C:\JDK\include和C:\JDK\include\win32加入到开发环境的IncludeFiles目录中, 同时将C:\JDK\lib目录添加到开发环境的LibraryFiles目录中。这三个目录是JNI定义的一些常量、结构及方法的头文件和库文件。 至此整个JNI的开发环境设置完毕,为了让此次JNI旅程能够顺利进行,还必须先准备一个Java类。在这个类中将用到Java中几乎所有有代表性的属性及方法,如静态方法与属性、数组、异常抛出与捕捉等。我们定义的Java程序(Demo.java)如下,本文中所有的代码演示都将基于该Java程序,代码如下:packagejni.test; /** *该类是为了演示JNI如何访问各种对象属性等 *@authorliudong publicclassDemo{ //用于演示如何访问静态的基本类型属性 publicstaticintCOUNT=8; //演示对象型属性 publicStringmsg;privateint[]counts;publicDemo(){this("缺省构造函数");}/***演示如何访问构造器*/publicDemo(Stringmsg){System.out.println(" *演示数组对象的访问 */publicint[]getCounts(){returncounts;}/***演示如何构造一个数组对象*/publicvoidsetCounts(int[]counts){this.counts=counts;}/***演示异常的捕捉*/publicvoidthrowExcp()throwsIllegalAccessException{ thrownewIllegalAccessException("exceptionoccur."); 本地代码在调用Java方法之前必须先加载Java虚拟机,而后所有的Java程序都在虚拟机中执行。 为了初始化Java虚拟机,JNI提供了一系列的接口函数InvocationAPI。通过这些API可以很方便地将虚拟机加载到内存中。创建虚拟机可以用函数jintJNI_CreateJavaVM(JavaVM**pvm,void**penv,void*args)。对于这个函数有一点需要注意的是,在JDK1.1中第三个参数总是指向一个结构JDK1_1InitArgs,这个结构无法完全在所有版本的虚拟机中进行无缝移植。在JDK1.2中已经使用了一个标准的初始化结构JavaVMInitArgs来替代JDK1_1InitArgs。下面我们分别给出两种不同版本的示例代码。在JDK1.1初始化虚拟机: #include JDK1.2初始化虚拟机: /*invoke2.c*/#include 初始化了Java虚拟机后,就可以开始调用Java的方法。要调用一个Java对象的方法必须经过几个步骤: 有两种途径来获取对象的类定义: 第一种是在已知类名的情况下使用FindClass来查找对应的类。但是要注意类名并不同于平时写的Java代码,例如要得到类jni.test.Demo的定义必须调用如下代码:jclasscls=(*env)->FindClass(env,"jni/test/Demo");//把点号换成斜杠 然后通过对象直接得到其所对应的类定义: jclasscls=(*env)->GetObjectClass(env,obj);//其中obj是要引用的对象,类型是jobject 我们先来看看JNI中获取方法定义的函数:jmethodID(JNICALL*GetMethodID)(JNIEnv*env,jclassclazz,constchar*name,constchar*sig);jmethodID(JNICALL*GetStaticMethodID)(JNIEnv*env,jclassclass,constchar*name,constchar*sig); 这两个函数的区别在于GetStaticMethodID是用来获取静态方法的定义,GetMethodID则是获取非静态的方法定义。这两个函数都需要提供四个参数:env就是初始化虚拟机得到的JNI环境;第二个参数class是对象的类定义,也就是第一步得到的obj;第三个参数是方法名称;最重要的是第四个参数,这个参数是方法的定义。因为我们知道Java中允许方法的多态,仅仅是通过方法名并没有办法定位到一个具体的方法,因此需要第四个参数来指定方法的具体定义。但是怎么利用一个字符串来表示方法的具体定义呢?JDK中已经准备好一个反编译工具javap,通过这个工具就可以得到类中每个属性、方法的定义。下面就来看看jni.test.Demo的定义: 打开命令行窗口并运行javap-s-pjni.test.Demo得到运行结果如下:CompiledfromDemo.javapublicclassjni.test.Demoextendsjava.lang.Object{publicstaticintCOUNT;/*I*/publicjava.lang.Stringmsg;/*Ljava/lang/String;*/privateintcounts[];/*[I*/publicjni.test.Demo();/*()V*/publicjni.test.Demo(java.lang.String);/*(Ljava/lang/String;)V*/publicjava.lang.StringgetMessage();/*()Ljava/lang/String;*/publicintgetCounts()[];/*()[I*/publicvoidsetCounts(int[]);/*([I)V*/publicvoidthrowExcp()throwsjava.lang.IllegalAccessException;/*()V*/static{};/*()V*/} 我们看到类中每个属性和方法下面都有一段注释。注释中不包含空格的内容就是第四个参数要填的内容(关于javap具体参数请查询JDK的使用帮助)。下面这段代码演示如何访问jni.test.Demo的getMessage方法: 假设我们已经有一个jni.test.Demo的实例obj*/ jmethodIDmid; jclasscls=(*env)->GetObjectClass(env,obj);//获取实例的类定义mid=(*env)->GetMethodID(env,cls,"getMessage","()Ljava/lang/String;");/*如果mid为0表示获取方法定义失败*/ jstringmsg=(*env)->CallObjectMethod(env,obj,mid); 如果该方法是静态的方法那只需要将最后一句代码改为以下写法即可:jstringmsg=(*env)->CallStaticObjectMethod(env,cls,mid);*/ 为了调用对象的某个方法,可以使用函数Call 访问类的属性与访问类的方法大体上是一致的,只不过是把方法变成属性而已。 这一步与访问类方法的第一步完全相同,具体使用参看访问类方法的第一步。 在JNI中是这样定义获取类属性的方法的: jfieldID(JNICALL*GetFieldID) (JNIEnv*env,jclassclazz,constchar*name,constchar*sig); jfieldID(JNICALL*GetStaticFieldID) 这两个函数中第一个参数为JNI环境;clazz为类的定义;name为属性名称;第四个参数同样是为了表达属性的类型。前面我们使用javap工具获取类的详细定义的时候有这样两行:publicjava.lang.Stringmsg; /*Ljava/lang/String;*/ 其中第二行注释的内容就是第四个参数要填的信息,这跟访问类方法时是相同的。 jstringmsg=(*env)->GetObjectField(env,cls,field);//msg就是对应Demo的msg jfieldIDfield2=(*env)->GetStaticFieldID(env,obj,"COUNT","I"); jintcount=(*env)->GetStaticIntField(env,cls,field2); 很多人刚刚接触JNI的时候往往会在这一节遇到问题,查遍了整个jni.h看到这样一个函数NewObject,它应该是可以用来访问类的构造函数。但是该函数需要提供构造函数的方法定义,其类型是jmethodID。从前面的内容我们知道要获取方法的定义首先要知道方法的名称,但是构造函数的名称怎么来填写呢?其实访问构造函数与访问一个普通的类方法大体上是一样的,惟一不同的只是方法名称不同及方法调用时不同而已。访问类的构造函数时方法名必须填写“ jclasscls=(*env)->FindClass(env,"jni/test/Demo"); 首先通过类的名称获取类的定义,相当于Java中的Class.forName方法 if(cls==0) jmethodIDmid=(*env)->GetMethodID(env,cls," if(mid==0) jobjectdemo=(*env)->NewObject(env,cls,mid,0); 访问构造函数必须使用NewObject的函数来调用前面获取的构造函数的定义 上面的代码我们构造了一个Demo的实例并传一个空串null 6.1创建一个新数组 要创建一个数组,我们首先应该知道数组元素的类型及数组长度。JNI定义了一批数组的类型j (*env)->NewIntArray(env,10); 6.2访问数组中的数据 访问数组首先应该知道数组的长度及元素的类型。现在我们把创建的数组中的每个元素值打印出来,代码如下: inti; 中文字符的处理往往是让人比较头疼的事情,特别是使用Java语言开发的软件,在JNI这个问题更加突出。由于Java中所有的字符都是Unicode编码,但是在本地方法中,例如用VC编写的程序,如果没有特殊的定义一般都没有使用Unicode的编码方式。为了让本地方法能够访问Java中定义的中文字符及Java访问本地方法产生的中文字符串,我定义了两个方法用来做相互转换。·方法一,将Java中文字符串转为本地字符串 第一个参数是虚拟机的环境指针 第二个参数为待转换的Java字符串定义 第三个参数是本地存储转换后字符串的内存块 第三个参数是内存块的大小 intJStringToChar(JNIEnv*env,jstringstr,LPTSTRdesc,intdesc_len) intlen=0; if(desc==NULL||str==NULL) return-1; //在VC中wchar_t是用来存储宽字节字符(UNICODE)的数据类型 wchar_t*w_buffer=newwchar_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); //调用字符编码转换函数(Win32API)将UNICODE转为ASCII编码格式字符串 //关于函数WideCharToMultiByte的使用请参考MSDN len=WideCharToMultiByte(CP_ACP,0,w_buffer,1024,desc,desc_len,NULL,NULL); //len=wcslen(w_buffer); if(len>0&&len desc[len]=0; delete[]w_buffer; returnstrlen(desc); ·方法二,将C的字符串转为Java能识别的Unicode字符串 jstringNewJString(JNIEnv*env,LPCTSTRstr) if(!env||!str) return0; jchar*buffer=newjchar[slen]; intlen=MultiByteToWideChar(CP_ACP,0,str,strlen(str),buffer,slen); if(len>0&&len buffer[len]=0; jstringjs=env->NewString(buffer,len); delete[]buffer; returnjs; 由于调用了Java的方法,因此难免产生操作的异常信息。这些异常没有办法通过C++本身的异常处理机制来捕捉到,但JNI可以通过一些函数来获取Java中抛出的异常信息。之前我们在Demo类中定义了一个方法throwExcp,下面将访问该方法并捕捉其抛出来的异常信息,代码如下: /**假设我们已经构造了一个Demo的实例obj,其类定义为cls*/jthrowableexcp=0;/*异常信息定义*/jmethodIDmid=(*env)->GetMethodID(env,cls,"throwExcp","()V");/*如果mid为0表示获取方法定义失败*/jstringmsg=(*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程序中只需要初始化一个虚拟机就可以了。所有人都是这样想的,但是一旦子线程访问主线程创建的虚拟机环境变量,系统就会出现错误对话框,然后整个程序终止。其实这里面涉及到两个概念,它们分别是虚拟机(JavaVM*jvm)和虚拟机环境(JNIEnv*env)。真正消耗大量系统资源的是jvm而不是env,jvm是允许多个线程访问的,但是env只能被创建它本身的线程所访问,而且每个线程必须创建自己的虚拟机环境env。这时候会有人提出疑问,主线程在初始化虚拟机的时候就创建了虚拟机环境env。为了让子线程能够创建自己的env,JNI提供了两个函数:AttachCurrentThread和DetachCurrentThread。下面代码就是子线程访问Java方法的框架: DWORDWINAPIThreadProc(PVOIDdwParam) JavaVMjvm=(JavaVM*)dwParam;/*将虚拟机通过参数传入*/ JNIEnv*env; (*jvm)->AttachCurrentThread(jvm,&env,NULL); ......... (*jvm)->DetachCurrentThread(jvm); 文件名目录 hpi.dll[JRE]\binioser12.dll[JRE]\binjava.dll[JRE]\binnet.dll[JRE]\binverify.dll[JRE]\binzip.dll[JRE]\binjvm.dll[JRE]\bin\classicrt.jar[JRE]\libtzmappings[JRE]\lib由于rt.jar有差不多10MB,但是其中有很大一部分文件并不需要,可以根据实际的应用情况进行删除。例如程序如果没有用到JavaSwing,就可以把涉及到Swing的文件都删除后重新打包。 一.C/C++调用Java 在C/C++中调用Java的方法一般分为五个步骤:初始化虚拟机、获取类、创建类对象、调用方法和退出虚拟机。 1.初始化虚拟机 代码如下: JavaVM*jvm; JavaVMInitArgsvm_args; JavaVMOptionoptions[3]; intres; //设置参数 options[0].optionString="-Djava.compiler=NONE"; //classpath有多个时,UNIX下以“:”分割。 options[1].optionString="-Djava.class.path=."; options[2].optionString="-verbose:jni"; vm_args.version=JNI_VERSION_1_4; vm_args.nOptions=3; vm_args.options=options; vm_args.ignoreUnrecognized=JNI_TRUE; res=JNI_CreateJavaVM(&jvm,(void**)&env,&vm_args); if(res>=0) //创建虚拟机成功 一个应用程序只需要一个虚拟机,但是每个线程需要自己的虚拟机运行环境。我们从一个虚拟机获取多个当前线程的运行环境,代码如下: intresult=0; result=jvm->AttachCurrentThread(reinterpret_cast if(result>=0) //获取运行环境成功 当线程退出时,需要释放本线程使用的运行环境。 jvm->DetachCurrentThread(); 2获取类 在进行方法调用之前,需要先获取相应的类,类名称必须包括包名,其中的“.”用“/”代替。 jclassJavaClass; JavaClass=env->FindClass("com/test/TestInterface"); if(JavaClass!=0) //获取成功 3创建类对象 jobjectobj; jmethodIDctor; ctor=env->GetMethodID(JavaClass," if(ctor!=0)//获取方法成功 obj=env->NewObject(JavaClass,ctor); 4调用方法 调用一个方法需要两个步骤:获取方法句柄和调用方法。 jmethodIDmethodID=env->GetMethodID(JavaClass,"setTest","(I)V"); if(methodID!=0)//获取方法成功 env->CallVoidMethod(obj,methodID,12); GetStaticMethodID是用来获取静态方法的定义,GetMethodID则是获取非静态的方法定义。他们传入参数的参数依次为:类定义、方法名称和方法的定义,方法的定义可以用jdk中带的javap工具反编译class文件获取,其格式如下: publicvoidsetTest(intinTest); Signature:(I)V Signature后面的内容就是方法的定义。 CallVoidMethod是对获取的方法进行调用,JNI接口中提供了一系列的同类方法,包括静态方法的调用函数(如:CallStaticXXXMethod)和非静态的方法(如:CallXXXMethod),其中XXX表示的不同方法返回类型,包括int、object等等。 5退出虚拟机 退出虚拟机调用方法如下: jvm->DestroyJavaVM(); 在JNI接口定义中,只有最后一个线程退出时,该方法才会返回,但是我只用一个线程,调用该方法也无法返回。故此建议系统退出时执行该方法,或者整个程序退出时,让虚拟机自己释放。 [注意]: l在处理中文字符串时,需要注意Java的char是双字节的,采用Unicode编码,在和C++中的char转换时,需要用到系统API:WideCharToMultiByte和MultiByteToWideChar。 l注意对运行环境中对象引用时的释放,以免引起内存泄漏。 wchar_t*w_buffer=(wchar_t*)env->GetStringChars(str,0); env->ReleaseStringChars(str,(constunsignedshort*)w_buffer); 6处理异常 C/C++中调用Java时,一定要捕获并处理Java方法抛出的异常信息,否则可能导致C/C++进程的核心转储(CoreDump)。 异常应在每个方法调用后检查: msg=(jstring)env->CallObjectMethod(obj,mid); if(env->ExceptionOccurred()) 二.Java调用C/C++ Java调用C/C++时,遵循几个步骤: 3、用“javah–jniXXX”命令从该class文件生成C语言头文件(XXX.h)。 5、在Java程序中使用System.loadLibrary(XXX)加载该库文件(需要设置-Djava.library.path环境变量指向该库文件存放路径)。 2009-06-1914:36 JNI中,JNIEnv*指针变量只对当前线程有效。如果是其他的线程,需要先获得JVM*指针,然后再获得当前线程的JNIEnv*指针。部分示例代码为: /**Invoker.cpp,Invoker.java*/ #include"Invoker.h" #include"invoker_include.h" JNIEnv*static_env; jobject*jObject;//线程间公用,必须使用globalreference jclassc;//必须使用globalreference jmethodIDm;//必须使用globalreference /***************************** *Class:Invoker *Method:register *****************************/ JNIEXPORTvoidJNICALLJava_Invoker_register(JNIEnv*env,jobjectarg) jObject=arg; //printf("object:%x,%x.\n",&arg,&jObject); printf("[main]Invokerregistered.\n"); jclassbgpClass=(*env)->GetObjectClass(env,arg); jmethodIDmethodId=(*env)->GetMethodID(env,bgpClass,"invoke","()V"); printf("[main]-class:%d,method:%d\n",bgpClass,methodId); (*env)->CallVoidMethod(env,arg,methodId); //Globalreference (*env)->GetJavaVM(env,&jvm); jObject=(*env)->NewGlobalRef(env,arg); c=(*env)->NewGlobalRef(env,bgpClass); m=(*env)->NewGlobalRef(env,methodId); start(invoke_java_method); (*env)->DeleteGlobalRef(env,c);//手动销毁globalreference (*env)->DeleteGlobalRef(env,m);//手动销毁globalreference (*env)->DeleteGlobalRef(env,jObject); (*jvm)->DetachCurrentThread(jvm);//销毁线程 (*jvm)->DestroyJavaVM(jvm);//?销毁虚拟机 //Testmethod JNIEXPORTvoidJNICALLJava_Invoker_println(JNIEnv*env,jobjectobj,jstringstring) printf("[main]%s\n",str); //Callbackmethod回调函数 intinvoke_java_method() (*jvm)->AttachCurrentThread(jvm,(void**)&static_env,0);//获得当前线程可以使用的JNIEnv*指针 (*static_env)->CallVoidMethod(static_env,jObject,m);//调用Java方法 printf("[callback]javamethodinvoked,invokerclass:%x...\n",&jObject); java中要访问C++代码时,使用JNI是唯一选择.然而,在多线程的情况下,可能出现以下问题: 问题描述: 一个java对象通过JNI调用DLL中一个send()函数向服务器发送消息,不等服务器消息到来就立即返回.同时把JNI接口的指针JNIEnv*env,和jobjectobj保存在DLL中的变量里. 然而,JNI文档上说,JNI接口的指针JNIEnv*不能在c++的线程间共享,在我的程序中,如果接收线程试图调用java对象的方法,程序会突然退出. 不知道有没有方法突破JNI接口的指针不能在多个c++线程中共享的限制 解决办法: staticJavaVM*gs_jvm; env->GetJavaVM(&gs_jvm);来获取JavaVM指针.获取了这个指针后,在DLL中的另一个线程里,可以调用: gs_jvm->AttachCurrentThread((void**)&env,NULL); 来将DLL中的线程"attachedtothevirtualmachine"(不知如何翻译...),同时获得了这个线程在jvm中的JNIEnv指针. 由于我需要做的是在DLL中的一个线程里改变某个java对象的值,所以,还必须获取那个java对象的jobject指针.同JNIEnv指针一样,jobject指针也不能在多个线程中共享.就是说,不能直接在保存一个线程中的jobject指针到全局变量中,然后在另外一个线程中使用它.幸运的是,可以用 gs_object=env->NewGlobalRef(obj); 来将传入的obj保存到gs_object中,从而其他线程可以使用这个gs_object来操纵那个java对象了. 示例代码如下: (1)java代码: //filename:Test.java importjava.io.*; classTestimplementsRunnable publicintvalue=0; privateThreadtx=null; publicTest() tx=newThread(this,"tx"); System.loadLibrary("Test"); publicnativevoidsetEnev(); Testt=newTest(); t.setEnev(); System.out.println("okinjavamain"); t.tx.start(); try Thread.sleep(10000000); }catch(Exceptione) System.out.println("errorinmain"); publicvoidrun() while(true) Thread.sleep(1000); System.out.println(value); System.out.println("errorinrun"); (2)DLL代码: //cppfilename:Test.cpp: #include"test.h" #include staticJavaVM*gs_jvm=NULL; staticjobjectgs_object=NULL; staticintgs_i=10; voidWINAPIThreadFun(PVOIDargv) jclasscls=env->GetObjectClass(gs_object); jfieldIDfieldPtr=env->GetFieldID(cls,"value","I"); while(1) Sleep(100); //在DLL中改变外面的java对象的value变量的值. env->SetIntField(gs_object,fieldPtr,(jint)gs_i++); JNIEXPORTvoidJNICALLJava_Test_setEnev(JNIEnv*env,jobjectobj) printf("comeintotest.dll\n"); //Returns“0”onsuccess;returnsanegativevalueonfailure. intretGvm=env->GetJavaVM(&gs_jvm); //直接保存obj到DLL中的全局变量是不行的,应该调用以下函数: HANDLEht=CreateThread(NULL,0, (LPTHREAD_START_ROUTINE)ThreadFun,0, NULL,NULL); printf("theHandlehtis:%d\n",ht); 一个java对象通过JNI调用DLL中一个send()函数向服务器发送消息,不等服务器消息到来就立即返回,同时把JNI接口的指针JNIEnv*env(虚拟机环境指针),和jobjectobj保存在DLL中的变量里. 解决此问题首先要明白造成这个问题的原因。那么崩溃的原因是什么呢? JNI文档上有明确表述:TheJNIEnvpointer,passedasthefirstargumenttoeverynativemethod,canonlybeusedinthethreadwithwhichitisassociated.ItiswrongtocachetheJNIEnvinterfacepointerobtainedfromonethread,andusethatpointerinanotherthread. 意思就是JNIEnv指针不能直接在多线程中共享使用。上面描述的程序崩溃的原因就在这里:回调时的线程和之前保存变量的线程共享了这个JNIEnv*env指针和jobjectobj变量。 于是,在第一个线程A中调用: JavaVM*gs_jvm; env->GetJavaVM(&gs_jvm);//来获取JavaVM指针.获取了这个指针后,将该JavaVM保存起来。 在另一个线程B里,调用 //这里就获得了B这个线程在jvm中的JNIEnv指针. 这里还必须获取那个java对象的jobject指针,因为我们要回调JAVA方法.同JNIEnv指针一样,jobject指针也不能在多个线程中共享.就是说,不能直接在保存一个线程中的jobject指针到全局变量中,然后在另外一个线程中使用它.幸运的是,可以用 来将传入的obj(局部变量)保存到gs_object中,从而其他线程可以使用这个gs_object(全局变量)来操纵这个java对象了. (1)java代码:Test.java: (2)DLL代码:Test.cpp: 1.#include"test.h" 2.#include 3.#include 4.staticJavaVM*gs_jvm=NULL; 5.staticjobjectgs_object=NULL; 6.staticintgs_i=10; 8.JNIEXPORTvoidJNICALLJava_Test_setEnev(JNIEnv*env,jobjectobj) 9.{ 10.env->GetJavaVM(&gs_jvm);//保存到全局变量中JVM 11.//直接赋值obj到DLL中的全局变量是不行的,应该调用以下函数: 12.gs_object=env->NewGlobalRef(obj); 13. 14.HANDLEht=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadFun,0,NULL,NULL); 15.} 16. 17.voidWINAPIThreadFun(PVOIDargv)//JNI中线程回调这个方法 18.{ 19.JNIEnv*env; 20.gs_jvm->AttachCurrentThread((void**)&env,NULL); 21.jclasscls=env->GetObjectClass(gs_object); 22.jfieldIDfieldPtr=env->GetFieldID(cls,"value","I"); 23. 24.while(1) 25.{ 26.Sleep(100); 27.//这里改变JAVA对象的属性值(回调JAVA) 28.env->SetIntField(gs_object,fieldPtr,(jint)gs_i++); 29.} 30.} 31. 32. JNI Therearecertainconstraintsthatyoumustkeepinmindwhenwritingnativemethodsthataretoruninamultithreadedenvironment.Byunderstandingandprogrammingwithintheseconstraints,yournativemethodswillexecutesafelynomatterhowmanythreadssimultaneouslyexecuteagivennativemethod.Forexample: JNI限制:Therearecertainconstraintsthatyoumustkeepinmindwhenwritingnativemethodsthataretoruninamultithreadedenvironment.Byunderstandingandprogrammingwithintheseconstraints,yournativemethodswillexecutesafelynomatterhowmanythreadssimultaneouslyexecuteagivennativemethod.Forexample:AJNIEnvpointerisonlyvalidinthethreadassociatedwithit.Youmustnotpassthispointerfromonethreadtoanother,orcacheanduseitinmultiplethreads. TheJavavirtualmachinepassesanativemethodthesameJNIEnvpointerinconsecutiveinvocationsfromthesamethread,butpassesdifferentJNIEnvpointerswheninvokingthatnativemethodfromdifferentthreads.AvoidthecommonmistakeofcachingtheJNIEnvpointerofonethreadandusingthepointerinanotherthread. Localreferencesarevalidonlyinthethreadthatcreatedthem.Youmustnotpasslocalreferencesfromonethreadtoanother.Youshouldalwaysconvertlocalreferencestoglobalreferenceswheneverthereisapossibilitythatmultiplethreadsmayusethesamereference. JNI的发展 JNI自从JDK1.1发行版以来一直是Java平台的一部分,并且在JDK1.2发行版中得到了扩展。JDK1.0发行版包含一个早期的本机方法接口,但是未明确分隔本机代码和Java代码。在这个接口中,本机代码可以直接进入JVM结构,因此无法跨JVM实现、平台或者甚至各种JDK版本进行移植。使用JDK1.0模型升级含有大量本机代码的应用程序,以及开发能支持多个JVM实现的本机代码的开销是极高的。 JDK1.1中引入的JNI支持: 有一个有趣的地方值得注意,一些较年轻的语言(如PHP)在它们的本机代码支持方面仍然在努力克服这些问题。 、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、 2009年7月27日 Java本机接口(JavaNativeInterface,JNI)是一个标准的JavaAPI,它支持将Java代码与使用其他编程语言编写的代码相集成。如果您希望利用已有的代码资源,那么可以使用JNI作为您工具包中的关键组件——比如在面向服务架构(SOA)和基于云的系统中。但是,如果在使用时未注意某些事项,则JNI会迅速导致应用程序性能低下且不稳定。本文将确定10大JNI编程缺陷,提供避免这些缺陷的最佳实践,并介绍可用于实现这些实践的工具。 Java环境和语言对于应用程序开发来说是非常安全和高效的。但是,一些应用程序却需要执行纯Java程序无法完成的一些任务,比如: JNI允许您完成这些任务。它明确分开了Java代码与本机代码(C/C++)的执行,定义了一个清晰的API在这两者之间进行通信。从很大程度上说,它避免了本机代码对JVM的直接内存引用,从而确保本机代码只需编写一次,并且可以跨不同的JVM实现或版本运行。 借助JNI,本机代码可以随意与Java对象交互,获取和设计字段值,以及调用方法,而不会像Java代码中的相同功能那样受到诸多限制。这种自由是一把双刃剑:它牺牲Java代码的安全性,换取了完成上述所列任务的能力。在您的应用程序中使用JNI提供了强大的、对机器资源(内存、I/O等)的低级访问,因此您不会像普通Java开发人员那样受到安全网的保护。JNI的灵活性和强大性带来了一些编程实践上的风险,比如导致性能较差、出现bug甚至程序崩溃。您必须格外留意应用程序中的代码,并使用良好的实践来保障应用程序的总体完整性。 本文介绍JNI用户最常遇到的10大编码和设计错误。其目标是帮助您认识到并避免它们,以便您可以编写安全、高效、性能出众的JNI代码。本文还将介绍一些用于在新代码或已有代码中查找这些问题的工具和技巧,并展示如何有效地应用它们。 JNI编程缺陷可以分为两类: 程序员在使用JNI时的5大性能缺陷如下: 要访问Java对象的字段并调用它们的方法,本机代码必须调用FindClass()、GetFieldID()、GetMethodId()和GetStaticMethodID()。对于GetFieldID()、GetMethodID()和GetStaticMethodID(),为特定类返回的ID不会在JVM进程的生存期内发生变化。但是,获取字段或方法的调用有时会需要在JVM中完成大量工作,因为字段和方法可能是从超类中继承而来的,这会让JVM向上遍历类层次结构来找到它们。由于ID对于特定类是相同的,因此您只需要查找一次,然后便可重复使用。同样,查找类对象的开销也很大,因此也应该缓存它们。 举例来说,清单1展示了调用静态方法所需的JNI代码: 清单1.使用JNI调用静态方法 intval=1;jmethodIDmethod;jclasscls;cls=(*env)->FindClass(env,"com/ibm/example/TestClass");if((*env)->ExceptionCheck(env)){returnERR_FIND_CLASS_FAILED;}method=(*env)->GetStaticMethodID(env,cls,"setInfo","(I)V");if((*env)->ExceptionCheck(env)){returnERR_GET_STATIC_METHOD_FAILED;}(*env)->CallStaticVoidMethod(env,cls,method,val);if((*env)->ExceptionCheck(env)){returnERR_CALL_STATIC_METHOD_FAILED;} 当我们每次希望调用方法时查找类和方法ID都会产生六个本机调用,而不是第一次缓存类和方法ID时需要的两个调用。 缓存会对您应用程序的运行时造成显著的影响。考虑下面两个版本的方法,它们的作用是相同的。清单2使用了缓存的字段ID: 清单2.使用缓存的字段ID intsumValues2(JNIEnv*env,jobjectobj,jobjectallValues){jintavalue=(*env)->GetIntField(env,allValues,a);jintbvalue=(*env)->GetIntField(env,allValues,b);jintcvalue=(*env)->GetIntField(env,allValues,c);jintdvalue=(*env)->GetIntField(env,allValues,d);jintevalue=(*env)->GetIntField(env,allValues,e);jintfvalue=(*env)->GetIntField(env,allValues,f);returnavalue+bvalue+cvalue+dvalue+evalue+fvalue;} 性能技巧#1 查找并全局缓存常用的类、字段ID和方法ID。 清单3没有使用缓存的字段ID: 清单3.未缓存字段ID intsumValues2(JNIEnv*env,jobjectobj,jobjectallValues){jclasscls=(*env)->GetObjectClass(env,allValues);jfieldIDa=(*env)->GetFieldID(env,cls,"a","I");jfieldIDb=(*env)->GetFieldID(env,cls,"b","I");jfieldIDc=(*env)->GetFieldID(env,cls,"c","I");jfieldIDd=(*env)->GetFieldID(env,cls,"d","I");jfieldIDe=(*env)->GetFieldID(env,cls,"e","I");jfieldIDf=(*env)->GetFieldID(env,cls,"f","I");jintavalue=(*env)->GetIntField(env,allValues,a);jintbvalue=(*env)->GetIntField(env,allValues,b);jintcvalue=(*env)->GetIntField(env,allValues,c);jintdvalue=(*env)->GetIntField(env,allValues,d);jintevalue=(*env)->GetIntField(env,allValues,e);jintfvalue=(*env)->GetIntField(env,allValues,f);returnavalue+bvalue+cvalue+dvalue+evalue+fvalue} 随后,这些调用可以复制被操作的元素。举例来说,如果您对含有1,000个元素的数组调用GetLongArrayElements(),则会造成至少分配或复制8,000字节的数据(每个long1,000元素*8字节)。当您随后使用ReleaseLongArrayElements()更新数组的内容时,需要另外复制8,000字节的数据来更新数组。即使您使用较新的GetPrimitiveArrayCritical(),规范仍然准许JVM创建完整数组的副本。 性能技巧#2 获取和更新仅本机代码需要的数组部分。在只要数组的一部分时通过适当的API调用来避免复制整个数组。 GetTypeArrayRegion()和SetTypeArrayRegion()方法允许您获取和更新数组的一部分,而不是整个数组。通过使用这些方法访问较大的数组,您可以确保只复制本机代码将要实际使用的数组部分。 举例来说,考虑相同方法的两个版本,如清单4所示: 清单4.相同方法的两个版本 jlonggetElement(JNIEnv*env,jobjectobj,jlongArrayarr_j,intelement){jbooleanisCopy;jlongresult;jlong*buffer_j=(*env)->GetLongArrayElements(env,arr_j,&isCopy);result=buffer_j[element];(*env)->ReleaseLongArrayElements(env,arr_j,buffer_j,0);returnresult;}jlonggetElement2(JNIEnv*env,jobjectobj,jlongArrayarr_j,intelement){jlongresult;(*env)->GetLongArrayRegion(env,arr_j,element,1,&result);returnresult;} 性能技巧#3 在单个API调用中尽可能多地获取或更新数组内容。如果可以一次较多地获取和更新数组内容,则不要逐个迭代数组中的元素。 在调用某个方法时,您经常会在传递一个有多个字段的对象以及单独传递字段之间做出选择。在面向对象设计中,传递对象通常能提供较好的封装,因为对象字段的变化不需要改变方法签名。但是,对于JNI来说,本机代码必须通过一个或多个JNI调用返回到JVM以获取需要的各个字段的值。这些额外的调用会带来额外的开销,因为从本机代码过渡到Java代码要比普通方法调用开销更大。因此,对于JNI来说,本机代码从传递进来的对象中访问大量单独字段时会导致性能降低。 考虑清单5中的两个方法,第二个方法假定我们缓存了字段ID: 清单5.两个方法版本 intsumValues(JNIEnv*env,jobjectobj,jinta,jintb,jintc,jintd,jinte,jintf){returna+b+c+d+e+f;}intsumValues2(JNIEnv*env,jobjectobj,jobjectallValues){jintavalue=(*env)->GetIntField(env,allValues,a);jintbvalue=(*env)->GetIntField(env,allValues,b);jintcvalue=(*env)->GetIntField(env,allValues,c);jintdvalue=(*env)->GetIntField(env,allValues,d);jintevalue=(*env)->GetIntField(env,allValues,e);jintfvalue=(*env)->GetIntField(env,allValues,f);returnavalue+bvalue+cvalue+dvalue+evalue+fvalue;} 性能技巧#4 如果可能,将各参数传递给JNI本机代码,以便本机代码回调JVM获取所需的数据。 sumValues2()方法需要6个JNI回调,并且运行10,000,000次需要3,572ms。其速度比sumValues()慢6倍,后者只需要596ms。通过传递JNI方法所需的数据,sumValues()避免了大量的JNI开销。 性能技巧#5 定义Java代码与本机代码之间的界限,最大限度地减少两者之间的互相调用。 因此,在设计Java代码与本机代码之间的界限时应该最大限度地减少两者之间的相互调用。消除不必要的越界调用,并且应该竭力在本机代码中弥补越界调用造成的成本损失。最大限度地减少越界调用的一个关键因素是确保数据处于Java/本机界限的正确一侧。如果数据未在正确的一侧,则另一侧访问数据的需求则会持续发起越界调用。 举例来说,如果我们希望使用JNI为某个串行端口提供接口,则可以构造两种不同的接口。第一个版本如清单6所示: 清单6.到串行端口的接口:版本1 清单7.到串行端口的接口:版本2 /***Initializestheserialportandreturnsanopaquehandletoanative*structurethatcontainsthehardwareaddressfortheserialport*andholdsinformationneededbytheserialportsuchas*thenextbuffertowritedatainto**@paramenvJNIenvthatcanbeusedbythemethod*@paramcomPortNamethenameoftheserialport*@returnsopaquehandletobepassedtosetSerialPortByteand*getSerialPortBytecalls*/jlonginitializeSerialPort2(JNIEnv*env,jobjectobj,jstringcomPortName);/***sendsabyteontheserialport**@paramenvJNIenvthatcanbeusedbythemethod*@paramserialPortConfigopaquehandlefortheserialport*@parambytethebytetobesent*/voidsendSerialPortByte(JNIEnv*env,jobjectobj,jlongserialPortConfig,jbytebyte);/***Readsthenextbytefromtheserialport**@paramenvJNIenvthatcanbeusedbythemethod*@paramserialPortConfigopaquehandlefortheserialport*@returnsthebytereadfromtheserialport*/jbytereadSerialPortByte(JNIEnv*env,jobjectobj,jlongserialPortConfig); 性能技巧#6 构造应用程序的数据,使它位于界限的正确的侧,并且可以由使用它的代码访问,而不需要大量跨界调用。 JNI函数返回的任何对象都会创建本地引用。举例来说,当您调用GetObjectArrayElement()时,将返回对数组中对象的本地引用。考虑清单8中的代码在运行一个很大的数组时会使用多少本地引用: 清单8.创建本地引用 voidworkOnArray(JNIEnv*env,jobjectobj,jarrayarray){jinti;jintcount=(*env)->GetArrayLength(env,array);for(i=0;i 每次调用GetObjectArrayElement()时都会为元素创建一个本地引用,并且直到本机代码运行完成时才会释放。数组越大,所创建的本地引用就越多。 性能技巧#7 当本机代码造成创建大量本地引用时,在各引用不再需要时删除它们。 这些本地引用会在本机方法终止时自动释放。JNI规范要求各本机代码至少能创建16个本地引用。虽然这对许多方法来说都已经足够了,但一些方法在其生存期中却需要更多的本地引用。对于这种情况,您应该删除不再需要的引用,方法是使用JNIDeleteLocalRef()调用,或者通知JVM您将使用更多的本地引用。 清单9.添加DeleteLocalRef() voidworkOnArray(JNIEnv*env,jobjectobj,jarrayarray){jinti;jintcount=(*env)->GetArrayLength(env,array);for(i=0;i 性能技巧#8 如果某本机代码将同时存在大量本地引用,则调用JNIEnsureLocalCapacity()方法通知JVM并允许它优化对本地引用的处理。 您可以调用JNIEnsureLocalCapacity()方法来通知JVM您将使用超过16个本地引用。这将允许JVM优化对该本机代码的本地引用的处理。如果无法创建所需的本地引用,或者JVM采用的本地引用管理方法与所使用的本地引用数量之间不匹配造成了性能低下,则未成功通知JVM会导致FatalError。 5大JNI正确性缺陷包括: 执行本机代码的线程使用JNIEnv发起JNI方法调用。但是,JNIEnv并不是仅仅用于分派所请求的方法。JNI规范规定每个JNIEnv对于线程来说都是本地的。JVM可以依赖于这一假设,将额外的线程本地信息存储在JNIEnv中。一个线程使用另一个线程中的JNIEnv会导致一些小bug和难以调试的崩溃问题。 正确性技巧#1 线程可以调用通过JavaVM对象使用JNI调用接口的GetEnv()来获取JNIEnv。JavaVM对象本身可以通过使用JNIEnv方法调用JNIGetJavaVM()来获取,并且可以被缓存以及跨线程共享。缓存JavaVM对象的副本将允许任何能访问缓存对象的线程在必要时获取对它自己的JNIEnv访问。要实现最优性能,线程应该绕过JNIEnv,因为查找它有时会需要大量的工作。 举例来说,考虑调用GetFieldID()的代码,如果无法找到所请求的字段,则会出现NoSuchFieldError。如果本机代码继续运行而未检测异常,并使用它认为应该返回的字段ID,则会造成程序崩溃。举例来说,如果Java类经过修改,导致charField字段不再存在,则清单10中的代码可能会造成程序崩溃—而不是抛出一个NoSuchFieldError: 清单10.未能检测异常 jclassobjectClass;jfieldIDfieldID;jcharresult=0;objectClass=(*env)->GetObjectClass(env,obj);fieldID=(*env)->GetFieldID(env,objectClass,"charField","C");result=(*env)->GetCharField(env,obj,fieldID); 正确性技巧#2 在发起可能会导致异常的JNI调用后始终检测异常。 添加异常检测代码要比在事后尝试调试崩溃简单很多。经常,您只需要检测是否出现了某个异常,如果是则立即返回Java代码以便抛出异常。然后,使用常规的Java异常处理流程处理它或者显示它。举例来说,清单11将检测异常: 清单11.检测异常 jclassobjectClass;jfieldIDfieldID;jcharresult=0;objectClass=(*env)->GetObjectClass(env,obj);fieldID=(*env)->GetFieldID(env,objectClass,"charField","C");if((*env)->ExceptionOccurred(env)){return;}result=(*env)->GetCharField(env,obj,fieldID); 不检测和清除异常会导致出现意外行为。您可以确定以下代码的问题吗? fieldID=(*env)->GetFieldID(env,objectClass,"charField","C");if(fieldID==NULL){fieldID=(*env)->GetFieldID(env,objectClass,"charField","D");}return(*env)->GetIntField(env,obj,fieldID); 问题在于,尽管代码处理了初始GetFieldID()未返回字段ID的情况,但它并未清除此调用将设置的异常。因此,本机返回的结果会造成立即抛出一个异常。 许多JNI方法都通过返回值来指示调用成功与否。与未检测异常相似,这也存在一个缺陷,即代码未检测返回值却假定调用成功而继续运行。对于大多数JNI方法来说,它们都设置了返回值和异常状态,这样应用程序更可以通过检测异常状态或返回值来判断方法运行正常与否。 正确性技巧#3 始终检测JNI方法的返回值,并包括用于处理错误的代码路径。 您可以确定以下代码的问题吗? clazz=(*env)->FindClass(env,"com/ibm/j9//HelloWorld");method=(*env)->GetStaticMethodID(env,clazz,"main","([Ljava/lang/String;)V");(*env)->CallStaticVoidMethod(env,clazz,method,NULL); 问题在于,如果未发现HelloWorld类,或者如果main()不存在,则本机将造成程序崩溃。 GetXXXArrayElements()和ReleaseXXXArrayElements()方法允许您请求任何元素。同样,GetPrimitiveArrayCritical()、ReleasePrimitiveArrayCritical()、GetStringCritical()和ReleaseStringCritical()允许您请求数组元素或字符串字节,以最大限度降低直接指向数组或字符串的可能性。这些方法的使用存在两个常见的缺陷。其一,忘记在ReleaseXXX()方法调用中提供更改。即便使用Critical版本,也无法保证您能获得对数组或字符串的直接引用。一些JVM始终返回一个副本,并且在这些JVM中,如果您在ReleaseXXX()调用中指定了JNI_ABORT,或者忘记调用了ReleaseXXX(),则对数组的更改不会被复制回去。 举例来说,考虑以下代码: voidmodifyArrayWithoutRelease(JNIEnv*env,jobjectobj,jarrayarr1){jbooleanisCopy;jbyte*buffer=(*env)->(*env)->GetByteArrayElements(env,arr1,&isCopy);if((*env)->ExceptionCheck(env))return;buffer[0]=1;} 正确性技巧#4 不要忘记为每个GetXXX()使用模式0(复制回去并释放内存)调用ReleaseXXX()。 在提供直接指向数组的指针的JVM上,该数组将被更新;但是,在返回副本的JVM上则不是如此。这会造成您的代码在一些JVM上能够正常运行,而在其他JVM上却会出错。您应该始终始终包括一个释放(release)调用,如清单12所示: 清单12.包括一个释放调用 voidmodifyArrayWithRelease(JNIEnv*env,jobjectobj,jarrayarr1){jbooleanisCopy;jbyte*buffer=(*env)->(*env)->GetByteArrayElements(env,arr1,&isCopy);if((*env)->ExceptionCheck(env))return;buffer[0]=1;(*env)->ReleaseByteArrayElements(env,arr1,buffer,JNI_COMMIT);if((*env)->ExceptionCheck(env))return;} 第二个缺陷是不注重规范对在GetXXXCritical()和ReleaseXXXCritical()之间执行的代码施加的限制。本机可能不会在这些方法之间发起任何调用,并且可能不会由于任何原因而阻塞。未重视这些限制会造成应用程序或JVM中出现间断性死锁。 举例来说,以下代码看上去可能没有问题: voidworkOnPrimitiveArray(JNIEnv*env,jobjectobj,jarrayarr1){jbooleanisCopy;jbyte*buffer=(*env)->GetPrimitiveArrayCritical(env,arr1,&isCopy);if((*env)->ExceptionCheck(env))return;processBufferHelper(buffer);(*env)->ReleasePrimitiveArrayCritical(env,arr1,buffer,0);if((*env)->ExceptionCheck(env))return;} 正确性技巧#5 确保代码不会在GetXXXCritical()和ReleaseXXXCritical()调用之间发起任何JNI调用或由于任何原因出现阻塞。 但是,我们需要验证在调用processBufferHelper()时可以运行的所有代码都没有违反任何限制。这些限制适用于在Get和Release调用之间执行的所有代码,无论它是不是本机的一部分。 本机可以创建一些全局引用,以保证对象在不再需要时才会被垃圾收集器回收。常见的缺陷包括忘记删除已创建的全局引用,或者完全失去对它们的跟踪。考虑一个本机创建了全局引用,但是未删除它或将它存储在某处: lostGlobalRef(JNIEnv*env,jobjectobj,jobjectkeepObj){jobjectgref=(*env)->NewGlobalRef(env,keepObj);} 正确性技巧#6 始终跟踪全局引用,并确保不再需要对象时删除它们。 创建全局引用时,JVM会将它添加到一个禁止垃圾收集的对象列表中。当本机返回时,它不仅会释放全局引用,应用程序还无法获取引用以便稍后释放它—因此,对象将会始终存在。不释放全局引用会造成各种问题,不仅因为它们会保持对象本身为活动状态,还因为它们会将通过该对象能接触到的所有对象都保持为活动状态。在某些情况下,这会显著加剧内存泄漏。 假设您编写了一些新JNI代码,或者继承了别处的某些JVI代码,如何才能确保避免了常见缺陷,或者在继承代码中发现它们?表1提供了一些确定这些常见缺陷的技巧: 表1.确定JNI编程缺陷的清单 未缓存 触发数组副本 错误界限 过多回访 使用大量本地引用 使用错误的JNIEnv 未检测异常 未检测返回值 未正确使用数组 未正确使用全局引用 规范验证 X 方法跟踪 转储 -verbose:jni 代码审查 您可以在开发周期的早期确定许多常见缺陷,方法如下: 维持规范的限制列表并审查本机与列表的遵从性是一个很好的实践,这可以通过手动或自动代码分析来完成。确保遵从性的工作可能会比调试由于违背限制而出现的细小和间断性故障轻松很多。下面提供了一个专门针对新开发代码(或对您来说是新的)的规范顺从性检查列表: IBM的JVM实现包括开启自动JNI检测的选项,其代价是较慢的执行速度。与出色的代码单元测试相结合,这是一种极为强大的工具。您可以运行应用程序或单元测试来执行遵从性检查,或者确定所遇到的bug是否是由本机引起的。除了执行上述规范遵从性检查之外,它还能确保: JNI检测报告的所有结论并不一定都是代码中的错误。它们还包括一些针对代码的建议,您应该仔细阅读它们以确保代码功能正常。 您可以通过以下命令行启用JNI检测选项: Usage:-Xcheck:jni:[option[,option[,...]]]allcheckapplicationandsystemclassesverbosetracecertainJNIfunctionsandactivitiestracetraceallJNIfunctionsnoboundsdonotperformboundscheckingonstringsandarraysnonfataldonotexitwhenerrorsaredetectednowarndonotdisplaywarningsnoadvicedonotdisplayadvicenovalistdonotcheckforva_listreusevalistcheckforva_listreusepedanticperformmorethorough,butslowercheckshelpprintthisscreen 使用IBMJVM的-Xcheck:jni选项作为标准开发流程的一部分可以帮助您更加轻松地找出代码错误。特别是,它可以帮助您确定在错误线程中使用JNIEnv以及未正确使用关键区域的缺陷的根源。 最新的SunJVM提供了一个类似的-Xcheck:jni选项。它的工作原理不同于IBM版本,并且提供了不同的信息,但是它们的作用是相同的。它会在发现未符合规范的代码时发出警告,并且可以帮助您确定常见的JNI缺陷。 生成对已调用本机方法以及这些本机方法发起的JNI回调的跟踪,这对确定大量常见缺陷的根源是非常有用的。可确定的问题包括: 一些JVM实现提供了一种可用于生存方法跟踪的机制。您还可以通过各种外部工具来生成跟踪,比如探查器和代码覆盖工具。 IBMJVM实现提供了许多用于生成跟踪信息的方法。第一种方法是使用-Xcheck:jni:trace选项。这将生成对已调用的本机方法以及它们发起的JNI回调的跟踪。清单13显示某个跟踪的摘录(为便于阅读,隔开了某些行): 清单13.IBMJVM实现所生成的方法跟踪 清单13中的跟踪摘录显示了已调用的本机方法(比如AccessController.initializeInternal()V)以及本机方法发起的JNI回调。 Sun和IBMJVM还提供了一个-verbose:jni选项。对于IBMJVM而言,开启此选项将提供关于当前JNI回调的信息。清单14显示了一个示例: 清单14.使用IBMJVM的-verbose:jni列出JNI回调 对于SunJVM而言,开启-verbose:jni选项不会提供关于当前调用的信息,但它会提供关于所使用的本机方法的额外信息。清单15显示了一个示例: 清单15.使用SunJVM的-verbose:jni [Dynamic-linkingnativemethodjava.util.zip.ZipFile.getMethod...JNI][Dynamic-linkingnativemethodjava.util.zip.Inflater.initIDs...JNI][Dynamic-linkingnativemethodjava.util.zip.Inflater.init...JNI][Dynamic-linkingnativemethodjava.util.zip.Inflater.inflateBytes...JNI][Dynamic-linkingnativemethodjava.util.zip.ZipFile.read...JNI][Dynamic-linkingnativemethodjava.lang.Package.getSystemPackage0...JNI][Dynamic-linkingnativemethodjava.util.zip.Inflater.reset...JNI] 开启此选项还会让JVM针对使用过多本地引用而未通知JVM的情况发起警告。举例来说,IBMJVM生成了这样一个消息: JVMJNCK065WJNIwarninginFindClass:Automaticallygrewlocalreferenceframecapacityfrom16to48.17referencesareinuse.UseEnsureLocalCapacityorPushLocalFrametoexplicitlygrowtheframe. 运行中的Java进程生成的转储包含大量关于JVM状态的信息。对于许多JVM来说,它们包括关于全局引用的信息。举例来说,最新的SunJVM在转储信息中包括这样一行: JNIglobalreferences:73 通过生成前后转储,您可以确定是否创建了任何未正常释放的全局引用。 您可以在UNIX环境中通过对java进程发起kill-3或kill-QUIT来请求转储。在Windows上,使用Ctrl+Break组合键。 对于IBMJVM,使用以下步骤获取关于全局引用的信息: 清单16.output.xml中的JNIGlobalReference条目 通过查看后续Java转储中报告的数值,您可以确定全局引用是否出现的泄漏。 intcalledALot(JNIEnv*env,jobjectobj,jobjectallValues){jclasscls=(*env)->GetObjectClass(env,allValues);jfieldIDa=(*env)->GetFieldID(env,cls,"a","I");jfieldIDb=(*env)->GetFieldID(env,cls,"b","I");jfieldIDc=(*env)->GetFieldID(env,cls,"c","I");jfieldIDd=(*env)->GetFieldID(env,cls,"d","I");jfieldIDe=(*env)->GetFieldID(env,cls,"e","I");jfieldIDf=(*env)->GetFieldID(env,cls,"f","I");}jclassgetObjectClassHelper(jobjectobject){/*usegloballycachedJNIEnv*/returncls=(*globalEnvStatic)->GetObjectClass(globalEnvStatic,allValues);} 代码审查可能会发现第一个方法未正确缓存字段ID,尽管重复使用了相同的ID,并且第二个方法所使用的JNIEnv并不在应该在的线程上。 现在,您已经了解了10大JNI编程缺陷,以及一些用于在已有或新代码中确定它们的良好实践。坚持应用这些实践有助于提高JNI代码的正确率,并且您的应用程序可以实现所需的性能水平。 有效集成已有代码资源的能力对于面向对象架构(SOA)和基于云的计算这两种技术的成功至关重要。JNI是一项非常重要的技术,用于将非Java旧有代码和组件集成到基于Java的平台中,充当SOA或基于云的系统的基本元素。正确使用JNI可以加速将这些组件转变为服务的过程,并允许您从现有投资中获得最大优势。 参考资料 学习 本文为在32位Windows平台上实现Java本地方法提供了实用的示例、步骤和准则。本文中的示例使用Sun公司的JavaDevelopmentKit(JDK)版本1.4.2。用C++语言编写的本地代码是用MicrosoftVisualC++6.0编译器编译生成。规定在Java程序中function/method称为方法,在C++程序中称为函数。 本文将围绕求圆面积逐步展开,探讨java程序如何调用现有的DLL?如何在C++程序中创建,检查及更新Java对象?如何在C++和Java程序中互抛异常,并进行异常处理?最后将探讨Eclipse及JBuilder工具可执行文件为什么不到100K大小以及所采用的技术方案? Java语言及其标准API应付应用程序的编写已绰绰有余。但在某些情况下,还是必须使用非Java代码,例如:打印、图像转换、访问硬件、访问现有的非Java代码等。与非Java代码的沟通要求获得编译器和JVM的专门支持,并需附加的工具将Java代码映射成非Java代码。目前,不同的开发商为我们提供了不同的方案,主要有以下方法: 1.JNI(JavaNativeInterface) 2.JRI(JavaRuntimeInterface) 3.J/Direct 4.RNI(RawNativeInterface) 5.Java/COM集成方案 6.CORBA(CommonObjectRequestBrokerArchitecture) 其中方案1是JDK自带的一部分,方案2由网景公司所提供,方案3、4、5是微软所提供的方案,方案6是一家非盈利组织开发的一种集成技术,利用它可以在由不同语言实现的对象之间进行“相互操作”的能力。 在开发过程中,我们一般采用第1种方案――JNI技术。因为只用当程序用MicrosoftJava编译器编译,而且只有在MicrosoftJava虚拟机(JVM)上运行的时候,才采用方案3、4、5。而方案6一般应用在大型的分布式应用中。 JNI是一种包容极广的编程接口,允许我们从Java应用程序里调用本地化方法。也就是说,JNI允许运行在虚拟机上的Java程序能够与其它语言(例如C/C++/汇编语言)编写的程序或者类库进行相互间的调用。同时JNI也提供了一整套的API,允许将Java虚拟机直接嵌入到本地的应用程序中。其中JNI所扮演的角色可用图一描述: 图一JNI基本结构描述图 目前JNI只能与用C和C++编写的本地化方法打交道。利用JNI,我们本地化方法可以: 1.创建、检查及更新Java对象 2.调用Java和非Java程序所编写的方法(函数),以及win32API等. 3.捕获和抛出“异常” 4.装载类并获取类信息 5.进行运行期类型检查 所以,原来在Java程序中能对类及对象所做的几乎所有事情都可以在本地化方法中实现。 下图表示了通过JNI,Java程序和非Java程序相互调用原理。 图二Java程序和非Java程序通过JNI相互调用原理 通过JNI,编写Java程序调用非Java程序一般步骤: 2.)利用头文件生成器javah生成本地化方法对应的头文件 3.)利用C和C++实现本地化方法(可调用非Java程序),并编译、链接生成DLL文件 4.)Java程序通过生成的DLL调用非Java程序 同时我们也可以通过JNI,将Java虚拟机直接嵌入到本地的应用程序中,步骤很简单,只需要在C/C++程序中以JNIAPI函数为媒介调用Java程序。 以上步骤虽简单,但有很多地方值得注意。如果一招不慎,可能造成满盘皆输。 任务:现有一求圆面积的Circle.dll(用MFC编写,参数:圆半径;返回值:圆面积)文件,在Java程序中调用该Dll。 实例1: packagecom.testJni; publicclassCircle publicnativevoidcAreas(intradius); //System.out.println(System.getProperty("java.library.path")); System.loadLibrary("CCircle"); 我们写一个Circle.bat批处理文件编译Circle.java文件,内容如下(可以用其他工具编译): javac-d.Circle.java javahcom.testJni.Circle pause 对于有包的情况一定要注意这一点,就是在用javah时有所不同。开始时我的程序始终运行都不成功,问题就出在这里。本类名称的前面均是包名。这样生成的头文件就是:com_testJni_Circle.h。开始时,在包含包的情况下我用javahCircle生成的头文件始终是Circle.h。在网上查资料时,看见别人的头文件名砸那长,我的那短。但不知道为什么,现在大家和我一样知道为什么了吧。:)。 如果是无包的情况,则将批处理文件换成如下内容: javacCircle.java javahCircle 3.2本地化方法实现 刚才生成的com_testJni_Circle.h头文件内容如下: /*Headerforclasscom_testJni_Circle*/ #ifndef_Included_com_testJni_Circle #define_Included_com_testJni_Circle *Class:com_testJni_Circle *Method:cAreas *Signature:(I)V JNIEXPORTvoidJNICALLJava_com_testJni_Circle_cAreas JNIEXPORTvoidJNICALLJava_com_testJni_Circle_cAreas(JNIEnv*env,jclassnewCircle,jintradius)。 这里JNIEXPORT和JNICALL都是JNI的关键字,其实是一些宏(具体参看jni_md.h文件)。 从以上头文件中,可以看出函数名生成规则为:Java[_包名]_类名_方法名[_函数签名](其中[]是可选项),均以字符下划线(_)分割。如果是无包的情况,则不包含[_包名]选项。如果本地化方法中有方法重载,则在该函数名最后面追加函数签名,也就是Signature对应的值,函数签名参见表一。 函数签名 Java类型 V void Z boolean B byte C char S short I J long F float D double Lfully-qualified-class; fully-qualified-class [type type[] (arg-types)ret-type methodtype 表一函数签名与Java类型的映射 在具体实现的时候,我们只关心函数原型: JNIEXPORTvoidJNICALLJava_com_testJni_Circle_cAreas(JNIEnv*,jobject,jint); 现在就让我们开始激动人心的一步吧:)。启动VC集成开发环境,新建一工程,在project里选择win32Dynamic-linkLibrary,输入工程名,然后点击ok,接下去步骤均取默认(图三)。如果不取默认,生成的工程将会有DllMain()函数,反之将无这个函数。我在这里取的是空。 图三新建DLL工程 然后选择菜单File->new->Files->C++SourceFile,生成一个空*.cpp文件,取名为CCircle。与3.1中System.loadLibrary("CCircle");参数保持一致。将JNIEXPORTvoidJNICALLJava_com_testJni_Circle_cAreas(JNIEnv*,jobject,jint);拷贝到CPP文件中,并包含其头文件。 对应的CCircle.cpp内容如下: #include #include"com_testJni_Circle.h" #include"windows.h" JNIEXPORTvoidJNICALLJava_com_testJni_Circle_cAreas(JNIEnv*env,jobjectnewCircle,jintradius) //调用求圆面积的Circle.dll typedefvoid(*PCircle)(intradius); HINSTANCEhDLL; PCircleCircle; hDLL=LoadLibrary("Circle.dll");//加载动态链接库Circle.dll文件 Circle=(PCircle)GetProcAddress(hDLL,"Circle"); Circle(8); FreeLibrary(hDLL);//卸载Circle.dll文件; 在编译前一定要注意下列情况。 注意:一定要把SDK目录下include文件夹及其下面的win32文件夹中的头文件拷贝到VC目录的include文件夹下。或者在VC的tools\options\directories中设置,如图四所示。 图四头文件设置 我们知道dll文件有两种指明导出函数的方法,一种是在.def文件中定义,另一种是在定义函数时使用关键字__declspec(dllexport)。而关键字JNIEXPORT实际在jni_md.h中如下定义, #defineJNIEXPORT__declspec(dllexport),可见JNI默认的导出函数使用第二种。使用第二种方式产生的导出函数名会根据编译器发生变化,在有的情况下会发生找不到导出函数的问题(我们在java控制台程序中调用很正常,但把它移植到JSP页面时,就发生了该问题,JVM开始崩溃,百思不得其解,后来加入一个.def文件才解决问题)。其实在《windows核心编程》一书中,第19.3.2节就明确指出创建用于非VisualC++工具的DLL时,建议加入一个def文件,告诉Microsoft编译器输出没有经过改变的函数名。因此最好采用第一种方法,定义一个.def文件来指明导出函数。本例中可以新建一个CCircle.def文件,内容如下: ;CCircle.def:DeclaresthemoduleparametersfortheDLL. LIBRARY"CCircle" DESCRIPTION'CCircleWindowsDynamicLinkLibrary' EXPORTS ;Explicitexportscangohere Java_com_testJni_Circle_cAreas 现在开始对所写的程序进行编译。选择build->rebuildall对所写的程序进行编译。点击build->buildCCirclee.DLL生成DLL文件。 也可以用命令行cl来编译。语法格式参见JDK文档JNI部分。 再次强调(曾经为这个东西大伤脑筋):DLL放置地方 1)当前目录。 2)Windows的系统目录及Windows目录 3)放在path所指的路径中 4)自己在path环境变量中设置一个路径,要注意所指引的路径应该到.dll文件的上一级,如果指到.dll,则会报错。 下面就开始测试我们的所写的DLL吧(假设DLL已放置正确)。 importcom.testJni.Circle; publicstaticvoidmain(Stringargvs[]) CirclemyCircle; myCircle=newCircle(); myCircle.cAreas(2); 编译,运行程序,将会弹出如下界面: 图五运行结果 以上是我们通过JNI方法调用的一个简单程序。实际情况要比这复杂的多。 现在开始来讨论JNI中参数的情况,我们来看一个程序片断。 实例二: JNIEXPORTjstringJNICALLJava_MyNative_cToJava (JNIEnv*env,jclassobj) charstr[]="Hello,word!\n"; jstr=env->NewStringUTF(str); returnjstr; Java语言 C/C++语言 bit位数 jboolean 8unsigned jbyte 8 jchar 16unsigned jshort 16 jint 32 jlong 64 jfloat jdouble 0 表二Java基本类型到本地类型的映射 表三Java中的类到本地类的映射 JNI函数NewStringUTF()是从一个包含UTF格式编码字符的char类型数组中创建一个新的jstring对象。jstring是以JNI为中介使Java的String类型与本地的string沟通的一种类型,我们可以视而不见(具体对应见表二和表三)。如果你使用的函数是GetStringUTFChars()(将jstring转换为UTF-8字符串),必须同时使用ReleaseStringUTFChars()函数,通过它来通知虚拟机去回收UTF-8串占用的内存,否则将会造成内存泄漏,最终导致系统崩溃。因为JVM在调用本地方法时,是在虚拟机中开辟了一块本地方法栈供本地方法使用,当本地方法使用完UTF-8串后,得释放所占用的内存。其中程序片断jstr=env->NewStringUTF(str);是C++中的写法,不必使用env指针。因为JNIEnv函数的C++版本包含有直接插入成员函数,他们负责查找函数指针。而对于C的写法,应改为:jstr=(*env)->NewStringUTF(env,str);因为所有JNI函数的调用都使用env指针,它是任意一个本地方法的第一个参数。env指针是指向一个函数指针表的指针。因此在每个JNI函数访问前加前缀(*env)->,以确保间接引用函数指针。 C/C++和Java互传参数需要自己在编程过程中仔细摸索与体味。 privateintcircleRadius; publicCircle() circleRadius=0; publicvoidsetCircleRadius(intradius) circleRadius=radius; publicvoidjavaAreas() floatPI=3.14f; if(circleRadius<=0) System.out.println(“error!”); System.out.println(PI*circleRadius*circleRadius); 在C++程序中访问Circle类中的private私有成员变量circleRadius,并设置它的值,同时调用Java方法javaAreas()。在函数Java_com_testJni_Circle_cAreas()中加入如下代码: jclasscircle; jmethodIDAreasID; jfieldIDradiusID; jintnewRadius=5; circle=env->GetObjectClass(newCircle);//getcurrentclass radiusID=env->GetFieldID(circle,"circleRadius","I");//getfieldID env->SetIntField(newCircle,radiusID,newRadius);//setfieldvalue AreasID=env->GetMethodID(circle,"javaAreas","()V");//getmethodID env->CallVoidMethod(newCircle,AreasID,NULL);//invokingmethod 在C++代码中,创建、检查及更新Java对象,首先要得到该类,然后再根据类得到其成员的ID,最后根据该类的对象,ID号调用成员变量或者成员方法。 得到类,有两个API函数,分别为FindClass()和GetObjectClass();后者顾名思义用于已经明确知道其对象,然后根据对象找类。前者用于得到没有实例对象的类。这里也可以改成circle=env->FidnClass("com/testJni/Circle");其中包的分隔符用字符"/"代替。如果已知一个类,也可以在C++代码中创建该类对象,其JNI函数为NewObject();示例代码如下: jclasscircle=env->FindClass("com/testJni/Circle"); jmethodIDcircleID=env->GetMethodID(circle," jobjectnewException=env->NewObject(circle,circleID,NULL); 其实JNIAPI函数名是很有规律的,从上面已窥全貌。获得成员方法的ID也是同样的分类方法。具体为GetMethodID()和GetStaticMethodID()。调用成员方法跟获得成员变量的值相类似,也根据其方法返回值的type不同而不同,分别为CalltypeMethod()和CallStatictypeMethod()。对于返回值为void的类型,其相应JNI函数为CallVoidMethod(); 以上获得成员ID函数的形参均一致。第一个参数为jclass,第二个参数为成员变量或方法,第三个参数为该成员的签名(签名可参见表一)。但调用或设置成员变量或方法时,第一个参数为实例对象(即jobject),其余形参与上面相同。 特别要注意的是得到构造方法的ID时,第二个参数不遵循上面的原则,为jmethodIDconstructorID=env->GetMethodID(jclass," 从上面代码中可以看出,在C++中可以访问java程序private类型的变量,严重破坏了类的封装原则。从而可以看出其不安全性。 本地化方法稳定性非常差,调用任何一个JNI函数都会出错,为了程序的健壮性,非常有必要在本地化方法中加入异常处理。我们继续修改上面的类。 importcom.testJni.*; publicclassRadiusIllegalextendsException protectedStringMSG="error!"; publicRadiusIllegal(Stringmessage) MSG=message; publicvoidprint() System.out.println(MSG); 同时也修改Circle.java中的方法,加入异常处理。 publicvoidjavaAreas()throwsRadiusIllegal//修改javaAreas(),加入异常处理 thrownewRadiusIllegal("warning:radiusisillegal!"); publicnativevoidcAreas(intradius)throwsRadiusIllegal;//修改cAreas(),加入异常处理 修改C++代码中的函数,加入异常处理,实现Java和C++互抛异常,并进行异常处理。 //此处省略部分代码 radiusIllegal=env->FindClass("com/testJni/RadiusIllegal");//gettheexceptionclass if((exception=env->ExceptionOccurred())!=NULL) cout<<"errorsincom_testJni_RadiusIllegal"< env->CallVoidMethod(newCircle,AreasID,NULL);//invoking if(env->IsInstanceOf(exception,radiusIllegal)==JNI_TRUE) cout<<"errorsinjavamethod"< cout<<"errorsininvokingjavaAreas()methodofCircle"< if(radius<=0) env->ThrowNew(radiusIllegal,"errorsinCfunction!");//throwexception //此处为调用计算圆面积的DLL 如果是C++的程序发生异常,则可以用JNIAPI函数ThrowNew()抛出该异常。但此时本地化方法并不返回退出,直到该程序执行完毕。所以当在本地化方法中发生异常时,应该人为的退出,及时进行处理,避免程序崩溃。函数ThrowNew()中第一个参数为jclass的类,第二个参数为附加信息,用来描述异常信息。 如果要知道异常发生的详细信息,或者对程序进行调试时,可以用函数ExceptionDescribe()来显示异常栈里面的内容。 可能大家天天都在用Eclipse和Jbulider这两款优秀的IDE进行程序开发,可能还不知道他们的可执行文件不到100KB大小,甚则连一副图片都可能比他们大。其实隐藏在他们背后的技术是JNI,可执行文件只是去启动Java程序,所以也只有那么小。 首先进行环境设置,在VC环境的tools-->options-->Directories下的Libraryfiles选项中包含其创建JVM的库文件jvm.lib,该库文件位于JDK\lib目录下,如图6所示: 图六库文件路径设置 接下来,我们在MFC程序(该程序请到《程序员》杂志频道下载)中进行创建JVM初始化工作。示例代码如下: jintres; memset(&vm_args,0,sizeof(vm_args)); //进行初始化工作 vm_args.version=JNI_VERSION_1_4;//版本号设置 res=JNI_CreateJavaVM(&jvm,(void**)&env,&vm_args);//创建JVM if(res<0) MessageBox("Can'tcreateJavaVM","Error",MB_OK|MB_ICONERROR); exit(1); cls=env->FindClass("prog"); if(env->ExceptionOccurred()!=NULL) MessageBox("Can'tfindProgclass!","Error",MB_OK|MB_ICONERROR); mid=env->GetStaticMethodID(cls,"main","([Ljava/lang/String;)V"); MessageBox("Can'tfindProg.main!","Error",MB_OK|MB_ICONERROR); env->CallStaticVoidMethod(cls,mid,NULL);//调用Java程序main()方法,启动Java程序 MessageBox("FatalError!","Error",MB_OK|MB_ICONERROR); jvm->DestroyJavaVM();//释放JVM资源 程序首先进行JVM初始化设置。我们观察jni.h文件关于JavaVMOption和JavaVMInitArgs的定义 typedefstructJavaVMOption{ char*optionString; void*extraInfo; }JavaVMOption; typedefstructJavaVMInitArgs{ jintversion; jintnOptions; JavaVMOption*options; jbooleanignoreUnrecognized; }JavaVMInitArgs; 结构体JavaVMInitArgs中有四个参数,我们在程序中都得必须设置。其中版本号一定要设置正确,不同的版本有不同的设置方法,关于版本1.1和1.2的设置方法参看sun公司的文档,这里只给出版本1.4的设置方法。第二个参数表示JavaVMOption结构体变量的维数,这里设置为三维,其中options[0].optionString="-Djava.compiler=NONE";表示disableJIT;options[1].optionString="-Djava.class.path=.";表示你所调用Java程序的Class文件的路径,这里设置为该exe应用程序的根路径(最后一个字符"."表示根路径);options[2].optionString="-verbose:jni";用于跟踪运行时的信息。第三个参数是一个JavaVMOption的指针变量。第四个参数意思我们可以参看帮助文档的解释IfignoreUnrecognizedisJNI_FALSE,JNI_CreateJavaVMreturnsJNI_ERRassoonasitencountersanyunrecognizedoptionstrings。 初始化完毕后,就可以调用创建JVM的函数jintJNICALLJNI_CreateJavaVM(JavaVM**pvm,void**penv,void*args);如果返回值小于0表示创建JVM失败。最可能的原因就是jvm.dll和jvm.lib设置错误。 如果在运行的过程中找不到java程序的类,那么就是-Djava.class.path设置错误。只要JVM创建成功,就可以根据上面的方法调用java程序。最后当程序结束后,调用函数DestroyJavaVM()摧毁JVM,释放资源。 七、附录 利用JNI函数,我们可以从本地化方法的内部与JVM打交道。正如在前面的例子中所看到的那样,每个本地化方法中都会接收一个特殊的自变量作为自己的第一个参数:JNIEnv――它是指向类型为JNIEnv_的一个特殊JNI数据结构的指针。JNI数据结构的一个元素是指向由JVM生成的一个指针的数组;该数组的每个元素都是指向一个JNI函数的指针。可以从本地化方法的内部对JNI函数的调用。第二个参数会根据Java类中本地方法的定义不同而不同,如果是定义为static方法,类型会是jclass,表示对特定Class对象的引用,如果是非static方法,类型是jobject,表示当前对象的引用,相当于”this”。可以说这两个变量是本地化方法返回JAVA的大门。 注意:在本地化方法中生成的Dll不具备到处运行的特性,而具有”牵一发而动全身”的特点。只要包名一改变,那么你所有的工作就得重新做一遍。原因就是当用javah生成头文件时,函数名的生成规则为Java[_包名]_类名_方法名[_函数签名];当你的包名改变时,生成的函数名也跟着改变了,那么你再次调用以前编写的Dll时,会抛出异常。 八、参考文献 1.《Java编程思想》BruceEckel机械工业出版社 2.《Java2核心技术卷2》(第6版)CayS.Horstmann,GaryCornell机械工业出版社 3.《高级Java2大学教程》(英文版)HarveyM.Deitel,PaulJ.Deitel,SeanE.Santry电子工业出版社 4.《windows核心编程》JeffreyRichter机械工业出版社 6.sun公司文档 注:本文最初发表在2004年《开发高手》第12期上。 ===================================================================== 》至此在Editplus中调试Jawin/NJawin的例子,可以通过。而在Eclipse中有时还会出上面的错误:COMException:nojawininjava.library.path。》在Eclipse中,菜单->window->preference->Java->installedJREs将原来的remove,重新建一个指到你的javasdk目录。》ok了。3、程序测试: 》调用dll,dll的方式不需要导出了,直接调用就可以了,下面是下载的包中提供的一个例子:》我在win2000下,测试通过。/**CreatedonDec22,2005**/importorg.jawin.FuncPtr; importorg.jawin.ReturnFlags; try{ FuncPtrmsgBox=newFuncPtr("USER32.DLL","MessageBoxW"); msgBox.invoke_I(0,"HelloFromaDLL","FromJawin",0,ReturnFlags.CHECK_NONE); }catch(Exceptione){ e.printStackTrace(); 但是这还没有完。需要记住的是,程序是以C/C++语言编写的,因此,无法使用自动垃圾回收,因为程序不是使用Java语言编写的。一旦线程完成JNI调用,它需要通过调用DetachCurrentThread来释放接口指针。如果未做此调用并且线程存在,进程将无法正常终止。相反,它将一直等待现在已不存在的线程以DestroyJavaVM调用的方式从JVM中离开。5.环境设置公共接口IMouseDownListener和IEventSource接口定义在common.h中。IMouseDownListener只有一个方法:onMouseDown()。该方法接收鼠标单击的屏幕位置。IEventSource接口包含了addMouseDownListener()和removeMouseDownListener()方法,用于注册和取消注册侦听器。JavaInvocationAPI的帮助例程有7个必需的常用工具方法可用于简化JavaInvocationAPI的使用,它们定义在Jvm.h中,在Jvm.cpp中实现: 其余方法都自己附带有解释: javah-classpath.\java\binevents.EventSourceProxy main.exe"-Djava.class.path=.\\java\\bin" "-Djava.library.path=.\\cpp\\listener\\Debug" 得到的控制台输出如下: InCMouseDownListener::onMouseDown X=50 Y=100 InMouseDownListener.onMouseDown 正如您从控制台输出所看到的,Java侦听器产生与出于解释目的而构建的本地侦听器相同的结果。结束语本文展示了如何为本地应用程序生成的事件注册一个Java类作为侦听器。通过使用观察者设计模式,您已经减少了事件源与侦听器之间的耦合。您还通过使用代理设计模式隐藏了来自Java侦听器的事件源的实现细节。您可以使用该设计模式组合来将一个JavaUI添加到现有的本地应用程序。 Ch23:JNIandThreadTheJavavirtualmachinesupportsmultiplethreadsofcontrolconcurrentlyexecutinginthesameaddressspace.Thisconcurrencyintroducesadegreeofcomplexitythatyoudonothaveinasingle-threadedenvironment.Multiplethreadsmayaccessthesameobjects,thesamefiledescriptors--inshort,thesamesharedresources--atthesametime. Togetthemostoutofthissection,youshouldbefamiliarwiththeconceptsofmultithreadedprogramming.YoushouldknowhowtowriteJavaapplicationsthatutilizemultiplethreadsandhowtosynchronizeaccessofsharedresources.AgoodreferenceonmultithreadedprogrammingintheJavaprogramminglanguageisConcurrentProgramminginJavaTM,DesignPrinciplesandPatterns,byDougLea(Addison-Wesley,1997). MonitorsaretheprimitivesynchronizationmechanismontheJavaplatform.Eachobjectcanbedynamicallyassociatedwithamonitor.TheJNIallowsyoutosynchronizeusingthesemonitors,thusimplementingthefunctionalityequivalenttoasynchronizedblockintheJavaprogramminglanguage: synchronized(obj){...//synchronizedblock}TheJavavirtualmachineguaranteesthatathreadacquiresthemonitorassociatedwiththeobjectobjbeforeitexecutesanystatementsintheblock.Thisensuresthattherecanbeatmostonethreadthatholdsthemonitorandexecutesinsidethesynchronizedblockatanygiventime.Athreadblockswhenitwaitsforanotherthreadtoexitamonitor. NativecodecanuseJNIfunctionstoperformequivalentsynchronizationonJNIreferences.YoucanusetheMonitorEnterfunctiontoenterthemonitorandtheMonitorExitfunctiontoexitthemonitor: if((*env)->MonitorEnter(env,obj)!=JNI_OK){.../*errorhandling*/}.../*synchronizedblock*/if((*env)->MonitorExit(env,obj)!=JNI_OK){.../*errorhandling*/};Executingthecodeabove,athreadmustfirstenterthemonitorassociatedwithobjbeforeexecutinganycodeinsidethesynchronizedblock.TheMonitor-Enteroperationtakesajobjectasanargumentandblocksifanotherthreadhasalreadyenteredthemonitorassociatedwiththejobject.CallingMonitorExitwhenthecurrentthreaddoesnotownthemonitorresultsinanerrorandcausesanIllegal-MonitorStateExceptiontoberaised.TheabovecodecontainsamatchedpairofMonitorEnterandMonitorExitcalls,yetwestillneedtocheckforpossibleerrors.Monitoroperationsmayfailif,forexample,theunderlyingthreadimplementationcannotallocatetheresourcesnecessarytoperformthemonitoroperation. MonitorEnterandMonitorExitworkonjclass,jstring,andjarraytypes,whicharespecialkindsofjobjectreferences. RemembertomatchaMonitorEntercallwiththeappropriatenumberofMonitorExitcalls,especiallyincodethathandleserrorsandexceptions: if((*env)->MonitorEnter(env,obj)!=JNI_OK)...;...if((*env)->ExceptionOccurred(env)){.../*exceptionhandling*//*remembertocallMonitorExithere*/if((*env)->MonitorExit(env,obj)!=JNI_OK)...;}.../*Normalexecutionpath.if((*env)->MonitorExit(env,obj)!=JNI_OK)...;FailuretocallMonitorExitwillmostlikelyleadtodeadlocks.BycomparingtheaboveCcodesegmentwiththecodesegmentatthebeginningofthissection,youcanappreciatehowmucheasieritistoprogramwiththeJavaprogramminglanguagethanwiththeJNI.Thus,itispreferabletoexpresssynchronizationconstructsintheJavaprogramminglanguage.If,forexample,astaticnativemethodneedstoenterthemonitorassociatedwithitsdefiningclass,youshoulddefineastaticsynchronizednativemethodasopposedtoperformingJNI-levelmonitorsynchronizationinnativecode. TheJavaAPIcontainsseveralothermethodsthatareusefulforthreadsynchronization.TheyareObject.wait,Object.notify,andObject.notifyAll.NoJNIfunctionsaresuppliedthatcorresponddirectlytothesemethodsbecausemonitorwaitandnotifyoperationsarenotasperformancecriticalasmonitorenterandexitoperations.NativecodemayinsteadusetheJNImethodcallmechanismtoinvokethecorrespondingmethodsintheJavaAPI: /*precomputedmethodIDs*/staticjmethodIDMID_Object_wait;staticjmethodIDMID_Object_notify;staticjmethodIDMID_Object_notifyAll;voidJNU_MonitorWait(JNIEnv*env,jobjectobject,jlongtimeout){(*env)->CallVoidMethod(env,object,MID_Object_wait,timeout);}voidJNU_MonitorNotify(JNIEnv*env,jobjectobject){(*env)->CallVoidMethod(env,object,MID_Object_notify);}voidJNU_MonitorNotifyAll(JNIEnv*env,jobjectobject){(*env)->CallVoidMethod(env,object,MID_Object_notifyAll);}WeassumethatthemethodIDsforObject.wait,Object.notify,andObject.notifyAllhavebeencalculatedelsewhereandarecachedintheglobalvariables.LikeintheJavaprogramminglanguage,youcancalltheabovemonitor-relatedfunctionsonlywhenholdingthemonitorassociatedwiththejobjectargument. WeexplainedearlierthataJNIEnvpointerisonlyvalidinitsassociatedthread.ThisisgenerallynotaproblemfornativemethodsbecausetheyreceivetheJNIEnvpointerfromthevirtualmachineasthefirstargument.Occasionally,however,itmaybenecessaryforapieceofnativecodenotcalleddirectlyfromthevirtualmachinetoobtaintheJNIEnvinterfacepointerthatbelongstothecurrentthread.Forexample,thepieceofnativecodemaybelongtoa"callback"functioncalledbytheoperatingsystem,inwhichcasetheJNIEnvpointerwillprobablynotbeavailableasanargument. YoucanobtaintheJNIEnvpointerforthecurrentthreadbycallingtheAttachCurrentThreadfunctionoftheinvocationinterface: JavaVM*jvm;/*alreadyset*/f(){JNIEnv*env;(*jvm)->AttachCurrentThread(jvm,(void**)&env,NULL);.../*useenv*/}Whenthecurrentthreadisalreadyattachedtothevirtualmachine,Attach-Current-ThreadreturnstheJNIEnvinterfacepointerthatbelongstothecurrentthread. TherearemanywaystoobtaintheJavaVMpointer:byrecordingitwhenthevirtualmachineiscreated,byqueryingforthecreatedvirtualmachinesusingJNI_GetCreatedJavaVMs,bycallingtheJNIfunctionGetJavaVMinsidearegularnativemethod,orbydefiningaJNI_OnLoadhandler.UnliketheJNIEnvpointer,theJavaVMpointerremainsvalidacrossmultiplethreadssoitcanbecachedinaglobalvariable. Java2SDKrelease1.2providesanewinvocationinterfacefunctionGetEnvsothatyoucancheckwhetherthecurrentthreadisattachedtothevirtualmachine,and,ifso,toreturntheJNIEnvpointerthatbelongstothecurrentthread.GetEnvandAttachCurrentThreadarefunctionallyequivalentifthecurrentthreadisalreadyattachedtothevirtualmachine. Supposethatnativecodetoberuninmultiplethreadsaccessesaglobalresource.ShouldthenativecodeuseJNIfunctionsMonitorEnterandMonitorExit,orusethenativethreadsynchronizationprimitivesinthehostenvironment(suchasmutex_lockonSolaris)Similarly,ifthenativecodeneedstocreateanewthread,shoulditcreateajava.lang.ThreadobjectandperformacallbackofThread.startthroughtheJNI,orshoulditusethenativethreadcreationprimitiveinthehostenvironment(suchasthr_createonSolaris) TheansweristhatalloftheseapproachesworkiftheJavavirtualmachineimplementationsupportsathreadmodelthatmatchesthatusedbythenativecode.Thethreadmodeldictateshowthesystemimplementsessentialthreadoperationssuchasscheduling,contextswitching,synchronization,andblockinginsystemcalls.Inanativethreadmodeltheoperatingsystemmanagesalltheessentialthreadoperations.Inauserthreadmodel,ontheotherhand,theapplicationcodeimplementsthethreadoperations.Forexample,the"Greenthread"modelshippedwithJDKandJava2SDKreleasesonSolarisusestheANSICfunctionssetjmpandlongjmptoimplementcontextswitches. Manymodernoperatingsystems(suchasSolarisandWin32)supportanativethreadmodel.Unfortunately,someoperatingsystemsstilllacknativethreadsupport.Instead,theremaybeoneormanyuserthreadpackagesontheseoperatingsystems. IfyouwriteapplicationstrictlyintheJavaprogramminglanguage,youneednotworryabouttheunderlyingthreadmodelofthevirtualmachineimplementation.TheJavaplatformcanbeportedtoanyhostenvironmentthatsupportstherequiredsetofthreadprimitives.MostnativeanduserthreadpackagesprovidethenecessarythreadprimitivesforimplementingaJavavirtualmachine. JNIprogrammers,ontheotherhand,mustpayattentiontothreadmodels.TheapplicationusingnativecodemaynotfunctionproperlyiftheJavavirtualimplementationandthenativecodehaveadifferentnotionofthreadingandsynchronization.Forexample,anativemethodcouldbeblockedinasynchronizationoperationinitsownthreadmodel,buttheJavavirtualmachine,runninginadifferentthreadmodel,maynotbeawarethatthethreadexecutingthenativemethodisblocked.Theapplicationdeadlocksbecausenootherthreadswillbescheduled. ThethreadmodelsmatchifthenativecodeusesthesamethreadmodelastheJavavirtualmachineimplementation.IftheJavavirtualmachineimplementationusesnativethreadsupport,thenativecodecanfreelyinvokethread-relatedprimitivesinthehostenvironment.IftheJavavirtualmachineimplementationisbasedonauserthreadpackage,thenativecodeshouldeitherlinkwiththesameuserthreadpackageorrelyonnothreadoperationsatall.Thelattermaybehardertoachievethanyouthink:mostClibrarycalls(suchasI/Oandmemoryallocationfunctions)performthreadsynchronizationunderneath.Unlessthenativecodeperformspurecomputationandmakesnolibrarycalls,itislikelytousethreadprimitivesindirectly. MostvirtualmachineimplementationssupportonlyaparticularthreadmodelforJNInativecode.Implementationsthatsupportnativethreadsarethemostflexible,hencenativethreads,whenavailable,aretypicallypreferredonagivenhostenvironment.Virtualmachineimplementationsthatrelyonaparticularuserthreadpackagemaybeseverelylimitedastothetypeofnativecodewithwhichtheycanoperate. Somevirtualmachineimplementationsmaysupportanumberofdifferentthreadmodels.Amoreflexibletypeofvirtualmachineimplementationmayevenallowyoutoprovideacustomthreadmodelimplementationforvirtualmachine'sinternaluse,thusensuringthatthevirtualmachineimplementationcanworkwithyournativecode.Beforeembarkingonaprojectlikelytorequirenativecode,youshouldconsultthedocumentationthatcomeswithyourvirtualmachineimplementationforthreadmodellimitations. Loadandunloadhandlersallowthenativelibrarytoexporttwofunctions:onetobecalledwhenSystem.loadLibraryloadsthenativelibrary,theothertobecalledwhenthevirtualmachineunloadsthenativelibrary.ThisfeaturewasaddedinJava2SDKrelease1.2. WhenSystem.loadLibraryloadsanativelibrary,thevirtualmachinesearchesforthefollowingexportedentryinthenativelibrary: JNIEXPORTjintJNICALLJNI_OnLoad(JavaVM*jvm,void*reserved);YoucaninvokeanyJNIfunctionsinanimplementationofJNI_Onload.AtypicaluseoftheJNI_OnLoadhandleriscachingtheJavaVMpointer,classreferences,ormethodandfieldIDs,asshowninthefollowingexample: WewillexplaininthenextsectionwhywecachetheCclassinaweakglobalreferenceinsteadofaglobalreference. JNIEnv*JNU_GetEnv(){JNIEnv*env;(*cached_jvm)->GetEnv(cached_jvm,(void**)&env,JNI_VERSION_1_2);returnenv;}8.4.2TheJNI_OnUnloadHandlerIntuitively,thevirtualmachinecallstheJNI_OnUnloadhandlerwhenitunloadsaJNInativelibrary.Thisisnotpreciseenough,however.WhendoesthevirtualmachinedeterminethatitcanunloadanativelibraryWhichthreadrunstheJNI_OnUnloadhandler Therulesofunloadingnativelibrariesareasfollows: HereisthedefinitionofaJNI_OnUnloadhandlerthatcleansuptheresourcesallocatedbytheJNI_OnLoadhandlerinthelastsection: JNIEXPORTvoidJNICALLJNI_OnUnload(JavaVM*jvm,void*reserved){JNIEnv*env;if((*jvm)->GetEnv(jvm,(void**)&env,JNI_VERSION_1_2)){return;}(*env)->DeleteWeakGlobalRef(env,Class_C);return;}TheJNI_OnUnloadfunctiondeletestheweakglobalreferencetotheCclasscreatedintheJNI_OnLoadhandler.WeneednotdeletethemethodIDMID_C_gbecausethevirtualmachineautomaticallyreclaimstheresourcesneededtorepresentC'smethodIDswhenunloadingitsdefiningclassC. WearenowreadytoexplainwhywecachetheCclassinaweakglobalreferenceinsteadofaglobalreference.AglobalreferencewouldkeepCalive,whichinturnwouldkeepC'sclassloaderalive.GiventhatthenativelibraryisassociatedwithC'sclassloaderL,thenativelibrarywouldnotbeunloadedandJNI_OnUnloadwouldnotbecalled. TheJNI_OnUnloadhandlerrunsinafinalizer.Incontrast,theJNI_OnLoadhandlerrunsinthethreadthatinitiatestheSystem.loadLibrarycall.BecauseJNI_OnUnloadrunsinanunknownthreadcontext,toavoidpossibledeadlocks,youshouldavoidcomplexsynchronizationandlockingoperationsinJNI_OnUnload.TheJNI_OnUnloadhandlertypicallycarriesoutsimpletaskssuchasreleasingtheresourcesallocatedbythenativelibrary. TheJNI_OnUnloadhandlerrunswhentheclassloaderthatloadedthelibraryandallclassesdefinedbythatclassloaderarenolongeralive.TheJNI_OnUnloadhandlermustnotusetheseclassesinanyway.IntheaboveJNI_OnUnloaddefinition,youmustnotperformanyoperationsthatassumeClass_Cstillreferstoavalidclass.TheDeleteWeakGlobalRefcallintheexamplefreesthememoryfortheweakglobalreferenceitself,butdoesnotmanipulatethereferredclassCinanyway. Insummary,youshouldbecarefulwhenwritingJNI_OnUnloadhandlers.Avoidcomplexlockingoperationsthatmayintroducedeadlocks.KeepinmindthatclasseshavebeenunloadedwhentheJNI_OnUnloadhandlerisinvoked. Reflectiongenerallyreferstomanipulatinglanguage-levelconstructsatruntime.Forexample,reflectionallowsyoutodiscoveratruntimethenameofarbitraryclassobjectsandthesetoffieldsandmethodsdefinedintheclass.ReflectionsupportisprovidedattheJavaprogramminglanguagelevelthroughthejava.lang.reflectpackageaswellassomemethodsinthejava.lang.Objectandjava.lang.Classclasses.AlthoughyoucanalwayscallthecorrespondingJavaAPItocarryoutreflectiveoperations,theJNIprovidesthefollowingfunctionstomakethefrequentreflectiveoperationsfromnativecodemoreefficientandconvenient: 库 列表A publicabstractclassSystemInformation{//public:................................................................/***Asimpleclasstorepresentdatasnapshotstakenby{@link#makeCPUUsageSnapshot}.*/publicstaticfinalclassCPUUsageSnapshot{publicfinallongm_time,m_CPUTime;//constructorisprivatetoensurethatmakeCPUUsageSnapshot()//isusedasthefactorymethodforthisclass:privateCPUUsageSnapshot(finallongtime,finallongCPUTime){m_time=time;m_CPUTime=CPUTime;}}//endofnestedclass //CustomexceptionclassforthrowingpublicstaticfinalclassNegativeCPUTimeextendsException{} /***Returnsthenumberofprocessorsonmachine*/publicstaticnativeintgetCPUs(); /***ReturnsCPU(kernel+user)timeusedbythecurrentprocess[inmilliseconds].*Thereturnedvalueisadjustedforthenumberofprocessorsinthesystem.*/publicstaticnativelonggetProcessCPUTime(); /***ReturnsCPU(kernel+user)timeusedbythecurrentprocess[inperecents].*ThereturnedvalueiseitherCPUpercentage,orzeroifthisisnotsupportedbyOS.*CurrentlyitissupportedbySolaris8,andnotsupportedbyWindowsXP*/publicstaticnativedoublegetProcessCPUPercentage(); /***Returnsmaximummemoryavailableinthesystem.*/publicstaticnativelonggetMaxMem(); /***Returnscurrentfreememoryinthesystem.*/publicstaticnativelonggetFreeMem(); /***Returnssystemnameinfolike"uname"commandoutput*/publicstaticnativeStringgetSysInfo(); /***ReturnsCPUusage(fractionof1.0)sofarbythecurrentprocess.Thisisatotal*forallprocessorssincetheprocesscreationtime.*/publicstaticnativedoublegetProcessCPUUsage(); /***Returnscurrentspaceallocatedfortheprocess,inKbytes.Thosepagesmayormaynotbeinmemory.*/publicstaticnativelonggetMemoryUsage(); /***Returnscurrentprocessspacebeingresidentinmemory,inKbytes.*/publicstaticnativelonggetMemoryResident(); /***SetsthesystemnativeprocessPIDforwhichallmeasurementswillbedone.*IfthismethodisnotcalledthenthecurrentJVMpidwillactasadefault.*Returnsthenative-dependenterrorcode,or0incaseofsuccess.*/publicstaticnativeintsetPid(intpid); /***Closesnative-dependentprocesshandle,ifnecessary.*/publicstaticnativeintdetachProcess(); //protected:............................................................. //package:............................................................... //private:...............................................................privateSystemInformation(){}//preventsubclassingprivatestaticfinalStringSILIB="silib";static{//loadinganativelibinastaticinitializerensuresthatitis//availabledonebeforeanymethodinthisclassiscalled:try{System.loadLibrary(SILIB);}catch(UnsatisfiedLinkErrore){System.out.println("nativelib'"+SILIB+"'notfoundin'java.library.path':"+System.getProperty("java.library.path"));throwe;//re-throw}} }//endofclass publicstaticdoublegetProcessCPUUsage(finalCPUUsageSnapshotstart,finalCPUUsageSnapshotend){if(start==null)thrownewIllegalArgumentException("nullinput:start");if(end==null)thrownewIllegalArgumentException("nullinput:end");if(end.m_time getCPUs()用来返回机器上处理器的个数getMaxMem()用来返回系统上可用的最大物理内存getFreeMem()用来返回系统上当前可用内存getSysInfo()用来返回系统信息,包括一些硬件和操作系统的详细信息getMemoryUsage()用来返回分配给进程的空间,以KB为单位(这些页面文件可能在内存里,也有可能不在内存里)getMemoryResident()用来返回当前进程驻留在内存里的空间,以KB为单位。 所有这些方法对于不同的Java开发人员来说常常都是非常有用的。为了确保本机JNI库被调入内存并在调用任何本机方法之前被初始化,这个库被加载到一个静态初始值里: static{try{System.loadLibrary(SILIB);}catch(UnsatisfiedLinkErrore){System.out.println("nativelib'"+SILIB+"'notfoundin'Java.library.path':"+System.getProperty("Java.library.path"));throwe;//re-throw}} finalSystemInformation.CPUUsageSnapshotm_prevSnapshot=SystemInformation.makeCPUUsageSnapshot();Thread.sleep(1000);finalSystemInformation.CPUUsageSnapshotevent=SystemInformation.makeCPUUsageSnapshot();finallongmemorySize=SystemInformation.getMemoryUsage();finallongresidentSize=SystemInformation.getMemoryResident();longfreemem=SystemInformation.getFreeMem()/1024;longmaxmem=SystemInformation.getMaxMem()/1024;doublereceivedCPUUsage=100.0*SystemInformation.getProcessCPUUsage(m_prevSnapshot,event);System.out.println("CurrentCPUusageis"+receivedCPUUsage+"%”); 现在让我们来分别看看针对Windows和Solaris的JNI本机实现。C头文件silib.h(列表B)能够用JDK里的Javah工具生成,或者手动编写。 列表B /*DONOTEDITTHISFILE-itismachinegenerated*/#include/*Headerforclasscom_vladium_utils_SystemInformation*/ #ifndef_Included_com_vladium_utils_SystemInformation#define_Included_com_vladium_utils_SystemInformation#ifdef__cplusplusextern"C"{#endif /**Class:com_vladium_utils_SystemInformation*Method:getProcessID*Signature:()I*/JNIEXPORTjintJNICALLJava_com_vladium_utils_SystemInformation_getProcessID(JNIEnv*,jclass); /**Class:com_vladium_utils_SystemInformation*Method:getCPUs*Signature:()I*/JNIEXPORTjintJNICALLJava_com_vladium_utils_SystemInformation_getCPUs(JNIEnv*,jclass); /**Class:com_vladium_utils_SystemInformation*Method:getProcessCPUTime*Signature:()J*/JNIEXPORTjlongJNICALLJava_com_vladium_utils_SystemInformation_getProcessCPUTime(JNIEnv*,jclass); /**Class:com_vladium_utils_SystemInformation*Method:getProcessCPUUsage*Signature:()D*/JNIEXPORTjdoubleJNICALLJava_com_vladium_utils_SystemInformation_getProcessCPUUsage(JNIEnv*,jclass); /**Class:com_vladium_utils_SystemInformation*Method:getPagefileUsage*Signature:()J*/JNIEXPORTjlongJNICALLJava_com_vladium_utils_SystemInformation_getPagefileUsage(JNIEnv*,jclass); #ifdef__cplusplus}#endif#endif Windows 首先我们来看看Windows的实现(列表C)。 列表C #include#include#include#include#include#include #include"com_vladium_utils_SystemInformation.h" staticjints_PID;staticHANDLEs_currentProcess;staticintalreadyDetached;staticints_numberOfProcessors;staticSYSTEM_INFOsystemInfo;staticWORDprocessorArchitecture;staticDWORDpageSize;staticDWORDprocessorType;staticWORDprocessorLevel;staticWORDprocessorRevision; #defineINFO_BUFFER_SIZE32768#defineBUFSIZE2048 /*-------------------------------------------------------------------------*/ /**AhelperfunctionforconvertingFILETIMEtoaLONGLONG[safefrommemory*alignmentpointofview].*/staticLONGLONGfileTimeToInt64(constFILETIME*time){ULARGE_INTEGER_time; _time.LowPart=time->dwLowDateTime;_time.HighPart=time->dwHighDateTime; return_time.QuadPart;}/*.........................................................................*/ /**ThismethodwasaddedinJNI1.2.Itisexecutedoncebeforeanyother*methodsarecalledandisostensiblyfornegotiatingJNIspecversions,but*canalsobeconvenientlyusedforinitializingvariablesthatwillnot*changethroughoutthelifetimeofthisprocess.*/JNIEXPORTjintJNICALLJNI_OnLoad(JavaVM*vm,void*reserved){ s_PID=_getpid();s_currentProcess=GetCurrentProcess();externalCPUmon=0;alreadyDetached=0; GetSystemInfo(&systemInfo);s_numberOfProcessors=systemInfo.dwNumberOfProcessors;processorArchitecture=systemInfo.wProcessorArchitecture;pageSize=systemInfo.dwPageSize;processorType=systemInfo.dwProcessorType;processorLevel=systemInfo.wProcessorLevel;processorRevision=systemInfo.wProcessorRevision; returnJNI_VERSION_1_2;}/*.........................................................................*/ JNIEXPORTvoidJNICALLJNI_OnUnload(JavaVM*vm,void*reserved){ if(!alreadyDetached&&s_currentProcess!=NULL){CloseHandle(s_currentProcess);printf("[JNIUnload]Detachedfromnativeprocess.");fflush(stdout);} }/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:getCPUs*Signature:()I*/JNIEXPORTjintJNICALLJava_com_vladium_utils_SystemInformation_getCPUs(JNIEnv*env,jclasscls){return(jint)s_numberOfProcessors;}/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:getSysInfo*Signature:()S*/JNIEXPORTjstringJNICALLJava_com_vladium_utils_SystemInformation_getSysInfo(JNIEnv*env,jclasscls){charbuf[2048];charbuf2[512];*buf=0;OSVERSIONINFOEXosvi;BOOLbOsVersionInfoEx;TCHARinfoBuf[INFO_BUFFER_SIZE];DWORDbufCharCount=INFO_BUFFER_SIZE; //TrycallingGetVersionExusingtheOSVERSIONINFOEXstructure.//Ifthatfails,tryusingtheOSVERSIONINFOstructure.ZeroMemory(&osvi,sizeof(OSVERSIONINFOEX));osvi.dwOSVersionInfoSize=sizeof(OSVERSIONINFOEX);if(!(bOsVersionInfoEx=GetVersionEx((OSVERSIONINFO*)&osvi))){osvi.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);if(!GetVersionEx((OSVERSIONINFO*)&osvi)){//Returnemptystringincaseofproblemsgotonext_label;}}switch(osvi.dwPlatformId){//TestfortheWindowsNTproductfamily.caseVER_PLATFORM_WIN32_NT: //Testforthespecificproduct.if(osvi.dwMajorVersion==5&&osvi.dwMinorVersion==2)strcat(buf,"WinServer2003,"); if(osvi.dwMajorVersion==5&&osvi.dwMinorVersion==1)strcat(buf,"WinXP"); if(osvi.dwMajorVersion==5&&osvi.dwMinorVersion==0)strcat(buf,"Win2K"); if(osvi.dwMajorVersion<=4)strcat(buf,"WinNT"); //TestforspecificproductonWindowsNT4.0SP6andlater.if(bOsVersionInfoEx){//Testfortheworkstationtype.if(osvi.wProductType==VER_NT_WORKSTATION){if(osvi.dwMajorVersion==4)strcat(buf,"Workstation4.0");elseif(osvi.wSuiteMask&VER_SUITE_PERSONAL)strcat(buf,"HomeEdition");elsestrcat(buf,"Professional");} //Testfortheservertype.elseif(osvi.wProductType==VER_NT_SERVER||osvi.wProductType==VER_NT_DOMAIN_CONTROLLER){if(osvi.dwMajorVersion==5&&osvi.dwMinorVersion==2){if(osvi.wSuiteMask&VER_SUITE_DATACENTER)strcat(buf,"DatacenterEdition");elseif(osvi.wSuiteMask&VER_SUITE_ENTERPRISE)strcat(buf,"EnterpriseEdition");elseif(osvi.wSuiteMask==VER_SUITE_BLADE)strcat(buf,"WebEdition");elsestrcat(buf,"StandardEdition");} elseif(osvi.dwMajorVersion==5&&osvi.dwMinorVersion==0){if(osvi.wSuiteMask&VER_SUITE_DATACENTER)strcat(buf,"DatacenterServer");elseif(osvi.wSuiteMask&VER_SUITE_ENTERPRISE)strcat(buf,"AdvancedServer");elsestrcat(buf,"Server");} else//WindowsNT4.0{if(osvi.wSuiteMask&VER_SUITE_ENTERPRISE)strcat(buf,"Server4.0,EnterpriseEdition");elsestrcat(buf,"Server4.0");}}}else//TestforspecificproductonWindowsNT4.0SP5andearlier{HKEYhKey;charszProductType[BUFSIZE];DWORDdwBufLen=BUFSIZE;LONGlRet; lRet=RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SYSTEMCurrentControlSetControlProductOptions",0,KEY_QUERY_VALUE,&hKey);if(lRet!=ERROR_SUCCESS){gotonext_label;} lRet=RegQueryValueEx(hKey,"ProductType",NULL,NULL,(LPBYTE)szProductType,&dwBufLen);if((lRet!=ERROR_SUCCESS)||(dwBufLen>BUFSIZE)){gotonext_label;} RegCloseKey(hKey); if(lstrcmpi("WINNT",szProductType)==0)strcat(buf,"Workstation");if(lstrcmpi("LANMANNT",szProductType)==0)strcat(buf,"Server");if(lstrcmpi("SERVERNT",szProductType)==0)strcat(buf,"AdvancedServer"); sprintf(buf2,"%d.%d",(int)osvi.dwMajorVersion,(int)osvi.dwMinorVersion);strcat(buf,buf2);} //Displayservicepack(ifany)andbuildnumber. if(osvi.dwMajorVersion==4&&lstrcmpi(osvi.szCSDVersion,"ServicePack6")==0){HKEYhKey;LONGlRet; //TestforSP6versusSP6a.lRet=RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SOFTWAREMicrosoftWindowsNTCurrentVersionHotfixQ246009",0,KEY_QUERY_VALUE,&hKey);if(lRet==ERROR_SUCCESS){sprintf(buf2,"SP6a(Build%d),",(int)(osvi.dwBuildNumber&0xFFFF));strcat(buf,buf2);}else//WindowsNT4.0priortoSP6a{sprintf(buf2,"%s(Build%d),",osvi.szCSDVersion,(int)(osvi.dwBuildNumber&0xFFFF));strcat(buf,buf2);} RegCloseKey(hKey);}else//notWindowsNT4.0{sprintf(buf2,"%s(Build%d),",osvi.szCSDVersion,(int)(osvi.dwBuildNumber&0xFFFF));strcat(buf,buf2);} break; //TestfortheWindowsMe/98/95.caseVER_PLATFORM_WIN32_WINDOWS: if(osvi.dwMajorVersion==4&&osvi.dwMinorVersion==0){strcat(buf,"Win95");if(osvi.szCSDVersion[1]=='C'||osvi.szCSDVersion[1]=='B')strcat(buf,"OSR2");} if(osvi.dwMajorVersion==4&&osvi.dwMinorVersion==10){strcat(buf,"Win98");if(osvi.szCSDVersion[1]=='A')strcat(buf,"SE");} if(osvi.dwMajorVersion==4&&osvi.dwMinorVersion==90){strcat(buf,"WinME");}break; caseVER_PLATFORM_WIN32s: strcat(buf,"Win32s");break;} next_label: strcat(buf,"on");//Getanddisplaythenameofthecomputer.bufCharCount=INFO_BUFFER_SIZE;if(!GetComputerName(infoBuf,&bufCharCount))gotonext_label_2;strcat(buf,infoBuf); next_label_2:strcat(buf,"(");if(!(osvi.dwPlatformId==VER_PLATFORM_WIN32_WINDOWS&&osvi.dwMajorVersion==4&&osvi.dwMinorVersion==0)){//Win95doesnotkeepCPUinfoinregistryLONGlRet;HKEYhKey;charszOrigCPUType[BUFSIZE];inti=0;DWORDdwBufLen=BUFSIZE;lRet=RegOpenKeyEx(HKEY_LOCAL_MACHINE,"HARDWAREDESCRIPTIONSystemCentralProcessor",0,KEY_QUERY_VALUE,&hKey);if(lRet!=ERROR_SUCCESS){gotonext_label_3;} strcat(buf,")"); jstringretval=(*env)->NewStringUTF(env,buf);returnretval;}/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:getProcessID*Signature:()I*/JNIEXPORTjintJNICALLJava_com_vladium_utils_SystemInformation_getProcessID(JNIEnv*env,jclasscls){returns_PID;}/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:setPid*Signature:()I*/JNIEXPORTjintJNICALLJava_com_vladium_utils_SystemInformation_setPid(JNIEnv*env,jclasscls,jintpid){DWORDerrCode;LPVOIDlpMsgBuf;s_PID=pid;s_currentProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);if(s_currentProcess==NULL){errCode=GetLastError();FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,NULL,errCode,MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),(LPTSTR)&lpMsgBuf,0,NULL); printf("[CPUmon]Couldnotattachtonativeprocess.Errorcode:%ldErrordescription:%s",errCode,lpMsgBuf);fflush(stdout);LocalFree(lpMsgBuf);returnerrCode;}printf("[CPUmon]Attachedtonativeprocess.");fflush(stdout);return0;}/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:detachProcess*Signature:()I*/JNIEXPORTjintJNICALLJava_com_vladium_utils_SystemInformation_detachProcess(JNIEnv*env,jclasscls){if(!alreadyDetached&&s_currentProcess!=NULL){CloseHandle(s_currentProcess);alreadyDetached=1;printf("[CPUmon]Detachedfromnativeprocess.");fflush(stdout);}return0;}/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:getProcessCPUTime*Signature:()J*/JNIEXPORTjlongJNICALLJava_com_vladium_utils_SystemInformation_getProcessCPUTime(JNIEnv*env,jclasscls){FILETIMEcreationTime,exitTime,kernelTime,userTime;DWORDerrCode;LPVOIDlpMsgBuf; BOOLresultSuccessful=GetProcessTimes(s_currentProcess,&creationTime,&exitTime,&kernelTime,&userTime);if(!resultSuccessful){errCode=GetLastError();FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,NULL,errCode,MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),(LPTSTR)&lpMsgBuf,0,NULL); printf("[CPUmon]AnerroroccuredwhiletryingtogetCPUtime.Errorcode:%ldErrordescription:%s",errCode,lpMsgBuf); fflush(stdout);LocalFree(lpMsgBuf);return-1;} return(jlong)((fileTimeToInt64(&kernelTime)+fileTimeToInt64(&userTime))/(s_numberOfProcessors*10000));}/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:getMaxMem*Signature:()J*/JNIEXPORTjlongJNICALLJava_com_vladium_utils_SystemInformation_getMaxMem(JNIEnv*env,jclasscls){MEMORYSTATUSstat;GlobalMemoryStatus(&stat);return(jlong)(stat.dwTotalPhys/1024);}/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:getFreeMem*Signature:()J*/JNIEXPORTjlongJNICALLJava_com_vladium_utils_SystemInformation_getFreeMem(JNIEnv*env,jclasscls){MEMORYSTATUSstat;GlobalMemoryStatus(&stat);return(jlong)(stat.dwAvailPhys/1024);}/*.........................................................................*/ /*defineminelapsedtime(inunitsof10E-7sec):*/#defineMIN_ELAPSED_TIME(10000) /**Class:com_vladium_utils_SystemInformation*Method:getProcessCPUUsage*Signature:()D*/JNIEXPORTjdoubleJNICALLJava_com_vladium_utils_SystemInformation_getProcessCPUUsage(JNIEnv*env,jclasscls){FILETIMEcreationTime,exitTime,kernelTime,userTime,nowTime;LONGLONGelapsedTime;DWORDerrCode;LPVOIDlpMsgBuf; printf("[CPUmon]AnerroroccuredwhiletryingtogetCPUtime.Errorcode:%ldErrordescription:%s",errCode,lpMsgBuf);fflush(stdout);LocalFree(lpMsgBuf);return-1.0;}GetSystemTimeAsFileTime(&nowTime); /*NOTE:win32systemtimeisnotveryprecise[~10msresolution],usesufficientlylongsamplingintervalsifyoumakeuseofthismethod.*/ elapsedTime=fileTimeToInt64(&nowTime)-fileTimeToInt64(&creationTime); if(elapsedTime /**Class:com_vladium_utils_SystemInformation*Method:getProcessCPUPercentage*Signature:()D*/JNIEXPORTjdoubleJNICALLJava_com_vladium_utils_SystemInformation_getProcessCPUPercentage(JNIEnv*env,jclasscls){//NotimplementedonWindowsreturn(jdouble)(-1.0);}/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:getMemoryUsage*Signature:()J*/JNIEXPORTjlongJNICALLJava_com_vladium_utils_SystemInformation_getMemoryUsage(JNIEnv*env,jclasscls){PROCESS_MEMORY_COUNTERSpmc; if(GetProcessMemoryInfo(s_currentProcess,&pmc,sizeof(pmc))){return(jlong)(pmc.PagefileUsage/1024);}else{return(jlong)(0);}}/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:getMemoryResident*Signature:()J*/JNIEXPORTjlongJNICALLJava_com_vladium_utils_SystemInformation_getMemoryResident(JNIEnv*env,jclasscls){PROCESS_MEMORY_COUNTERSpmc; if(GetProcessMemoryInfo(s_currentProcess,&pmc,sizeof(pmc))){return(jlong)(pmc.WorkingSetSize/1024);}else{return(jlong)(0);}} #undefMIN_ELAPSED_TIME /*-------------------------------------------------------------------------*//*endoffile*/ JNI里有两个特殊的函数——JNI_OnLoad和JNI_OnUnload,它们分别在加载和卸载库的时候被调用。JNI_OnLoad在调用其他任何方法之前被执行,而且能够很方便地用于初始化在这一进程的生命周期中没有发生变化的变量,并用于协调JNI规范的版本。在默认情况下,库会测量它自己的进程的参数,但是通过调用systemInformation.setPid()方法它可以从Java应用程序被重载。s_PIDC变量用来保存PID,而s_currentProcess用来保存进程句柄(用于Windows的是HANDLE类型,而用于Solaris的是int类型)。为了读取的一些参数,应该首先打开进程句柄,我们需要在库关闭使用的时候停止同一个进程句柄(通常它在JVM因为相同的原因而关闭的时候发生)。这就是JNI_OnUnload()函数起作用的地方。但是,JVM的一些实现事实上没有调用JNI_OnUnload(),还有发生句柄会永远打开的危险。为了降低这种可能性,我们应该在Java应用程序里加入一个明确调用detachProcess()C函数的关闭挂钩。下面就是我们加入关闭挂钩的方法: if(pid!=-1){intresult=SystemInformation.setPid(pid);if(result!=0){return;}hasPid=true;//CreateshutdownhookforproperprocessdetachRuntime.getRuntime().addShutdownHook(newThread(){publicvoidrun(){SystemInformation.detachProcess();}});} System.out.println("SysInfo:”+SystemInformation.getSysInfo()):SysInfo:WinXPProfessionalServicePack1(Build2600),onDBMOSWS2132(Intel(R)Xeon(TM)CPU1.70GHz) AndthesamecodeonSolariswillgive: SysInfo:SunOS5.8sxav-devGeneric_108528-29sun4usparcSUNW,Ultra-EnterpriseSun_Microsystems 列表D gcc-D_JNI_IMPLEMENTATION_-Wl,——kill-at-IC:/jdk1.3.1_12/include-IC:/jdk1.3.1_12/include/win32-sharedC:/cpu_profile/src/native/com_vladium_utils_SystemInformation.c-oC:/cpu_profile/dll/silib.dllC:/MinGW/lib/libpsapi.a这个库的Solaris实现见列表E和列表F.这两个都是C语言文件,应该被编译到一个共享库(.so)里。用于编译共享库的帮助器make.sh见列表G.所有基于Solaris系统的调用被移到列表F里,这使得列表E就是一个JNI的简单包装程序。Solaris实现要比Win32实现更加复杂,要求更多的临时数据结构、内核和进程表。列出的代码里有更多的注释。 列表E /*-------------------------------------------------------------------------*//**AnimplementationofJNImethodsincom.vladium.utils.SystemInformation*class.*ThisisaportedversionfromWin32toSolaris.**Forsimplicity,thisimplementaionassumesJNI1.2+andomitserrorhandling.**PortfromWin32byPeterV.Mikhalenko(C)2004,DeutscheBank[peter@mikhalenko.ru]*Originalsource(C)2002,VladimirRoubtsov[vlad@trilogy.com]*//*-------------------------------------------------------------------------*/ #include //HelperSolaris8-dependentexternalroutinesexternintsol_getCPUs();externintsol_getFreeMem();externintsol_getMaxMem();externlongintsol_getProcessCPUTime(intpid,intnproc);externdoublesol_getProcessCPUPercentage(intpid);externlongsol_getMemoryUsage(intpid);externlongsol_getMemoryResident(intpid);externchar*sol_getSysInfo();externvoidinitKVM(); staticjints_PID;staticints_numberOfProcessors;staticintexternalCPUmon;staticintalreadyDetached; /*.........................................................................*/ /**ThismethodwasaddedinJNI1.2.Itisexecutedoncebeforeanyother*methodsarecalledandisostensiblyfornegotiatingJNIspecversions,but*canalsobeconvenientlyusedforinitializingvariablesthatwillnot*changethroughoutthelifetimeofthisprocess.*/JNIEXPORTjintJNICALLJNI_OnLoad(JavaVM*vm,void*reserved){s_PID=_getpid();externalCPUmon=0;alreadyDetached=0; /*usekstattoupdateallprocessorinformation*/s_numberOfProcessors=sol_getCPUs();initKVM(); /**Class:com_vladium_utils_SystemInformation*Method:getSysInfo*Signature:()S*/JNIEXPORTjstringJNICALLJava_com_vladium_utils_SystemInformation_getSysInfo(JNIEnv*env,jclasscls){char*buf=sol_getSysInfo();jstringretval=(*env)->NewStringUTF(env,buf);free(buf);returnretval;}/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:setPid*Signature:()I*/JNIEXPORTjintJNICALLJava_com_vladium_utils_SystemInformation_setPid(JNIEnv*env,jclasscls,jintpid){s_PID=pid;externalCPUmon=1;printf("[CPUmon]Attachedtoprocess.");fflush(stdout);return0;}/*.........................................................................*/ *Class:com_vladium_utils_SystemInformation *Method:detachProcess *Signature:()I JNIEXPORTjintJNICALL Java_com_vladium_utils_SystemInformation_detachProcess(JNIEnv*env,jclasscls) if(externalCPUmon&&!alreadyDetached){ alreadyDetached=1;printf("[CPUmon]Detachedfromprocess.");fflush(stdout);}return0;}/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:getProcessCPUTime*Signature:()J*/JNIEXPORTjlongJNICALLJava_com_vladium_utils_SystemInformation_getProcessCPUTime(JNIEnv*env,jclasscls){return(jlong)sol_getProcessCPUTime((int)s_PID,s_numberOfProcessors);}/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:getMaxMem*Signature:()J*/JNIEXPORTjlongJNICALLJava_com_vladium_utils_SystemInformation_getMaxMem(JNIEnv*env,jclasscls){return(jlong)sol_getMaxMem();}/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:getFreeMem*Signature:()J*/JNIEXPORTjlongJNICALLJava_com_vladium_utils_SystemInformation_getFreeMem(JNIEnv*env,jclasscls){return(jlong)sol_getFreeMem();}/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:getProcessCPUUsage*Signature:()D*/JNIEXPORTjdoubleJNICALLJava_com_vladium_utils_SystemInformation_getProcessCPUUsage(JNIEnv*env,jclasscls){return0.0;}/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:getProcessCPUPercentage*Signature:()D*/JNIEXPORTjdoubleJNICALLJava_com_vladium_utils_SystemInformation_getProcessCPUPercentage(JNIEnv*env,jclasscls){return(jdouble)sol_getProcessCPUPercentage((int)s_PID);}/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:getMemoryUsage*Signature:()J*/JNIEXPORTjlongJNICALLJava_com_vladium_utils_SystemInformation_getMemoryUsage(JNIEnv*env,jclasscls){return(jlong)sol_getMemoryUsage((int)s_PID);}/*.........................................................................*/ /**Class:com_vladium_utils_SystemInformation*Method:getMemoryResident*Signature:()J*/JNIEXPORTjlongJNICALLJava_com_vladium_utils_SystemInformation_getMemoryResident(JNIEnv*env,jclasscls){return(jlong)sol_getMemoryResident((int)s_PID);} /*-------------------------------------------------------------------------*//*endoffile*/列表F/*-------------------------------------------------------------------------*//**Solaris-dependentsystemroutinesandkernelcalls*UsedforgettingmemoryandCPUconsumptionstatistics**Author:PeterV.Mikhalenko(C)2004,DeutscheBank[peter@mikhalenko.ru]*//*-------------------------------------------------------------------------*/ #include#ifndefKSTAT_DATA_UINT32#defineui32ul#endif#include#include#include#include#include#include#include#include #define_STRUCTURED_PROC1#include#defineprpsinfopsinfo#definepr_fillpr_nlwp/*TheserequireanANSICcompiler"Reissercpp"doesn'tlikethis*/#definepr_statepr_lwp.pr_state#definepr_oldpripr_lwp.pr_oldpri#definepr_nicepr_lwp.pr_nice#definepr_pripr_lwp.pr_pri#definepr_onpropr_lwp.pr_onpro#defineZOMBIE(p)((p)->pr_nlwp==0)#defineSIZE_K(p)((p)->pr_size)#defineRSS_K(p)((p)->pr_rssize)#definePROCFS"/proc" /*definitionsforindicesinthenlistarray*/#defineX_V0#defineX_MPID1#defineX_ANONINFO2#defineX_MAXMEM3#defineX_SWAPFS_MINFREE4#defineX_FREEMEM5#defineX_AVAILRMEM6#defineX_AVENRUN7#defineX_CPU8#defineX_NPROC9#defineX_NCPUS10 staticstructnlistnlst[]={,/*0*//*replacedbydynamicallocation*/,/*1*/#ifOSREV>=56/*thisstructurereallyhassomeextrafields,butthefirstthreematch*/,/*2*/#else,/*2*/#endif,/*3*//*usesysconf*/,/*4*//*usedonlyw/USE_ANONINFO*/,/*5*//*availablefromkstat>=2.5*/,/*6*//*availablefromkstat>=2.5*/,/*7*//*availablefromkstat*/,/*8*//*availablefromkstat*/,/*9*//*availablefromkstat*/,/*10*//*availablefromkstat*/ }; statickstat_ctl_t*kc=NULL;statickstat_t**cpu_ks;staticcpu_stat_t*cpu_stat;staticintncpus;kvm_t*kd;staticunsignedlongfreemem_offset;staticunsignedlongmaxmem_offset;staticunsignedlongfreemem=-1L;staticunsignedlongmaxmem=-1L; /*pagetokfunctionisreallyapointertoanappropriatefunction*/staticintpageshift;staticint(*p_pagetok)();#definepagetok(size)((*p_pagetok)(size)) intpagetok_none(intsize){return(size);} intpagetok_left(intsize){return(size< intpagetok_right(intsize){return(size>>pageshift);} #defineUPDKCID(nk,ok)if(nk==-1){perror("kstat_read");exit(1);}if(nk!=ok)gotokcid_changed; voidinitKVM(){inti; /*performthekvm_open-suppresserrorhere*/kd=kvm_open(NULL,NULL,NULL,O_RDONLY,NULL); /*calculatepageshiftvalue*/i=sysconf(_SC_PAGESIZE);pageshift=0;while((i>>=1)>0){pageshift++;} /*calculateanamounttoshifttoKvalues*//*rememberthatlogbase2of1024is10(i.e.:2^10=1024)*/pageshift-=10; /*nowdeterminewhichpageshiftfunctionisappropriatefortheresult(havetobecausex< #defineSI_LEN512#defineBUFSIZE256 char*sol_getSysInfo(){char*retbuf=(char*)malloc(SI_LEN);intcurend=0;intmaxl=SI_LEN;*retbuf=0;char*buf=(char*)malloc(BUFSIZE);longres=sysinfo(SI_SYSNAME,buf,BUFSIZE);if(res>0&&res<=maxl){strcat(retbuf,buf);curend=res-1;maxl=SI_LEN-curend;}if(curend0&&res<=maxl){strcat(retbuf,buf);curend+=res-1;maxl=SI_LEN-curend;}if(curend0&&res<=maxl){strcat(retbuf,buf);curend+=res-1;maxl=SI_LEN-curend;}if(curend0&&res<=maxl){strcat(retbuf,buf);curend+=res-1;maxl=SI_LEN-curend;}if(curend0&&res<=maxl){strcat(retbuf,buf);curend+=res-1;maxl=SI_LEN-curend;}if(curend0&&res<=maxl){strcat(retbuf,buf);curend+=res-1;maxl=SI_LEN-curend;}if(curend0&&res<=maxl){strcat(retbuf,buf);curend+=res-1;maxl=SI_LEN-curend;}if(curend0&&res<=maxl){strcat(retbuf,buf);curend+=res-1;maxl=SI_LEN-curend;}if(curendvalue.ui32>ncpus){ncpus=kn->value.ui32;cpu_ks=(kstat_t**)realloc(cpu_ks,ncpus*sizeof(kstat_t*));cpu_stat=(cpu_stat_t*)realloc(cpu_stat,ncpus*sizeof(cpu_stat_t));} for(ks=kc->kc_chain;ks;ks=ks->ks_next){if(strncmp(ks->ks_name,"cpu_stat",8)==0){nkcid=kstat_read(kc,ks,NULL);/*ifkcidchanged,pointermightbeinvalid*/UPDKCID(nkcid,kcid); cpu_ks[ncpu]=ks;ncpu++;if(ncpu>ncpus){fprintf(stderr,"kstatfindstoomanycpus:shouldbe%d",ncpus);exit(1);}}}/*notethatncpucouldbelessthanncpus,butthat'sokay*/changed=0;} /*returnthenumberofcpusfound*/ncpus=ncpu;returnncpu;} unsignedlongsol_getMaxMem(){maxmem=pagetok(sysconf(_SC_PHYS_PAGES));returnmaxmem;} unsignedlongsol_getFreeMem(){kstat_t*ks;kstat_named_t*kn;ks=kstat_lookup(kc,"unix",0,"system_pages");if(kstat_read(kc,ks,0)==-1){perror("kstat_read");exit(1);}if(kd!=NULL){/*alwaysgetfreememfromkvmifwecan*/(void)getkval(freemem_offset,(int*)(&freemem),sizeof(freemem),"freemem");}else{kn=kstat_data_lookup(ks,"freemem");if(kn)freemem=kn->value.ul;}return(unsignedlong)pagetok(freemem);} //Returnsthenumberofmilliseconds(notnanosecondsandseconds)elapsedonprocessor//sinceprocessstart.Thereturnedvalueisadjustedforthenumberofprocessorsinthesystem.longintsol_getProcessCPUTime(intpid,intnproc){structprpsinfocurrproc;intfd;charbuf[30];longintretval=0;snprintf(buf,sizeof(buf),"%s/%d/psinfo",PROCFS,pid);if((fd=open(buf,O_RDONLY))<0){return0L;}if(read(fd,&currproc,sizeof(psinfo_t))!=sizeof(psinfo_t)){(void)close(fd);return0L;}(void)close(fd);retval=(currproc.pr_time.tv_sec*1000+currproc.pr_time.tv_nsec/1000000)/nproc;returnretval;} //ReturnspercentageCPUbypid//InSolaris8itiscontainedinprocfsdoublesol_getProcessCPUPercentage(intpid){structprpsinfocurrproc;intfd;charbuf[30];doubleretval=0.0;snprintf(buf,sizeof(buf),"%s/%d/psinfo",PROCFS,pid);if((fd=open(buf,O_RDONLY))<0){return0;}if(read(fd,&currproc,sizeof(psinfo_t))!=sizeof(psinfo_t)){(void)close(fd);return0;}(void)close(fd);retval=((double)currproc.pr_pctcpu)/0x8000*100;returnretval;} //Returnscurrentspaceallocatedfortheprocess,inbytes.Thosepagesmayormaynotbeinmemory.longsol_getMemoryUsage(intpid){structprpsinfocurrproc;intfd;charbuf[30];doubleretval=0.0;snprintf(buf,sizeof(buf),"%s/%d/psinfo",PROCFS,pid);if((fd=open(buf,O_RDONLY))<0){return0;}if(read(fd,&currproc,sizeof(psinfo_t))!=sizeof(psinfo_t)){(void)close(fd);return0;}(void)close(fd);retval=currproc.pr_size;returnretval;} //Returnscurrentprocessspacebeingresidentinmemory.longsol_getMemoryResident(intpid){structprpsinfocurrproc;intfd;charbuf[30];doubleretval=0.0;snprintf(buf,sizeof(buf),"%s/%d/psinfo",PROCFS,pid);if((fd=open(buf,O_RDONLY))<0){return0;}if(read(fd,&currproc,sizeof(psinfo_t))!=sizeof(psinfo_t)){(void)close(fd);return0;}(void)close(fd);retval=currproc.pr_rssize;returnretval;} /**getkval(offset,ptr,size,refstr)-getavalueoutofthekernel.*"offset"isthebyteoffsetintothekernelforthedesiredvalue,*"ptr"pointstoabufferintowhichthevalueisretrieved,*"size"isthesizeofthebuffer(andtheobjecttoretrieve),*"refstr"isareferencestringusedwhenprintingerrormeessages,*if"refstr"startswitha'!',thenafailureonreadwillnot*befatal(thismayseemlikeasillywaytodothings,butI*reallydidn'twanttheoverheadofanotherargument).**/intgetkval(unsignedlongoffset,int*ptr,intsize,char*refstr){if(kvm_read(kd,offset,(char*)ptr,size)!=size){if(*refstr=='!'){return(0);}else{fprintf(stderr,"top:kvm_readfor%s:%s",refstr,strerror(errno));exit(23);}}return(1);} 列表G #!/bin/shgcc-G-lkvm-lkstatcom_vladium_utils_SystemInformation.c-olibsilib.sosolaris-extern.c#com_vladium_utils_SystemInformation.cisinListing-E#solaris-extern.cisinListing-F 开发环境安装及配置 开发测试用到的JAVA类 2.1开发JAVA类 在硬盘的任意地方新建一个名叫test的文件夹,本文档示例中将test文件夹建立在C盘根目录,然后在里面新建一个名称叫Demo.java的JAVA文件,将下面测试用的代码粘贴到该文件中。 publicclassDemo privateStringmsg; privateint[]counts; publicDemo() this("缺省构造函数"); *演示如何访问构造器 publicDemo(Stringmsg) this.msg=msg; this.counts=null; publicStringgetMessage() returnmsg; *该方法演示如何访问一个静态方法 publicstaticStringgetHelloWorld() return"Helloworld!"; *该方法演示参数的传入传出及中文字符的处理 publicStringappend(Stringstr,inti) returnstr+i; publicint[]getCounts() returncounts; *演示如何构造一个数组对象 publicvoidsetCounts(int[]counts) this.counts=counts; *演示异常的捕捉 publicvoidthrowExcp()throwsIllegalAccessException 2.2编译JAVA类 运行CMD控制台程序进入命令行模式,输入命令javac-classpathc:\c:\test\Demo.java,-classpath参数指定classpath的路径,这里就是test目录所在的路径。(注意:如果你没有将JDK的环境变量设置好,就需要先进入JDK的bin目录下,如下图所示。) 2.3查看方法的签名 我们知道Java中允许方法的多态,仅仅是通过方法名并没有办法定位到一个具体的方法,因此需要一个字符串来唯一表示一个方法。但是怎么利用一个字符串来表示方法的具体定义呢?JDK中已经准备好一个反编译工具javap,通过这个工具就可以得到类中每个属性、方法的签名。在CMD下运行javap-s-p-classpathc:\test.Demo即可看到属性和方法的签名。如下图红色矩形框起来的字符串为方法Stringappend(Stringstr,inti)的签名。 在VC中调用JAVA类 3.1快速调用JAVA中的函 在VC中新建一个控制台程序,然后新建一个CPP文件,将下面的代码添加到该文件中。运行该文件,即可得到Demo类中Stringappend(Stringstr,inti)函数返回的字符串。 #include usingnamespacestd; jstringNewJString(JNIEnv*env,LPCTSTRstr); stringJStringToCString(JNIEnv*env,jstringstr); intmain() //定义一个函数指针,下面用来指向JVM中的JNI_CreateJavaVM函数 typedefjint(WINAPI*PFunCreateJavaVM)(JavaVM**,void**,void*); /*设置初始化参数*/ //disableJIT,这是JNI文档中的解释,具体意义不是很清楚,能取哪些值也不清楚。 //从JNI文档里给的示例代码中搬过来的 //设置classpath,如果程序用到了第三方的JAR包,也可以在这里面包含进来 options[1].optionString="-Djava.class.path=.;c:\\"; //设置显示消息的类型,取值有gc、class和jni,如果一次取多个的话值之间用逗号格开,如-verbose:gc,class options[2].optionString="-verbose:NONE"; //设置版本号,版本号有JNI_VERSION_1_1,JNI_VERSION_1_2和JNI_VERSION_1_4 //选择一个根你安装的JRE版本最近的版本号即可,不过你的JRE版本一定要等于或者高于指定的版本号 //该参数指定是否忽略非标准的参数,如果填JNI_FLASE,当遇到非标准参数时,JNI_CreateJavaVM会返回JNI_ERR //加载JVM.DLL动态库 HINSTANCEhInstance=::LoadLibrary("C:\\j2sdk1.4.2_15\\jre\\bin\\client\\jvm.dll"); if(hInstance==NULL) returnfalse; //取得里面的JNI_CreateJavaVM函数指针 PFunCreateJavaVMfunCreateJavaVM=(PFunCreateJavaVM)::GetProcAddress(hInstance,"JNI_CreateJavaVM"); //调用JNI_CreateJavaVM创建虚拟机 res=(*funCreateJavaVM)(&jvm,(void**)&env,&vm_args); //查找test.Demo类,返回JAVA类的CLASS对象 jclasscls=env->FindClass("test/Demo"); //根据类的CLASS对象获取该类的实例 jobjectobj=env->AllocObject(cls); //获取类中的方法,最后一个参数是方法的签名,通过javap-s-p文件名可以获得 jmethodIDmid=env->GetMethodID(cls,"append","(Ljava/lang/String;I)Ljava/lang/String;"); //构造参数并调用对象的方法 constcharszTest[]="电信"; jstringarg=NewJString(env,szTest); jstringmsg=(jstring)env->CallObjectMethod(obj,mid,arg,12); cout< //销毁虚拟机并释放动态库 ::FreeLibrary(hInstance); stringJStringToCString(JNIEnv*env,jstringstr)//(jstringstr,LPTSTRdesc,intdesc_len) if(str==NULL) return""; intlen=env->GetStringLength(str); wchar_t*w_buffer=newwchar_t[len+1]; char*c_buffer=newchar[2*len+1]; ZeroMemory(w_buffer,(len+1)*sizeof(wchar_t)); constjchar*jcharString=env->GetStringChars(str,0); wcscpy(w_buffer,jcharString); env->ReleaseStringChars(str,jcharString); ZeroMemory(c_buffer,(2*len+1)*sizeof(char)); /调用字符编码转换函数(Win32API)将UNICODE转为ASCII编码格式字符串 len=WideCharToMultiByte(CP_ACP,0,w_buffer,len,c_buffer,2*len,NULL,NULL); stringcstr=c_buffer; delete[]c_buffer; returncstr; 3.2调用步骤分析及注意事项 a、加载jvm.dll动态库,然后获取里面的JNI_CreateJavaVM函数。这个步骤也可以通过在VC工程的LINK标签页里添加对jvm.lib的连接,然后在环境变量里把jvm.dll所在的路径加上去来实现。但后面这种方法在部署的时候会比前一个方法麻烦。 b、利用构造好的参数,调用JNI_CreateJavaVM函数创建JVM。JNI_CreateJavaVM函数内部会自动根据jvm.dll的路径来获取JRE的环境,所以千万不要把jvm.dll文件拷贝到别的地方,然后再通过LoadLibrary函数导入。 e、利用FindClass返回的class对象,调用GetMethodID函数可以获得里面方法的ID,在这里GetMethodID函数传入了三个参数:第一个参数是class对象,因为方法属于某个具体的类;第二个参数是方法的名称;第三个参数是方法的签名,这个签名可以在前面3.3中介绍的方法获得。 f、利用class对象,可以通过调用AllocObject函数获得该class对象对应类的一个实例,即Demo类的对象。 g、利用上面获取的函数ID和Demo类的对象,就可以通过CallObjectMethod函数调用相应的方法,该函数的参数跟printf函数的参数一样,个数是不定的。第一个参数是类的对象;第二个参数是要调用的方法的ID;后面的参数就是需要传给调用的JAVA类方法的参数,如果调用的JAVA类方法没有参数,则调用CallObjectMethod时传前两个参数就可以了。 h、从上面的示例中可以看到,在调用JAVA的方法前,构造传入的字符串时,用到了NewJString函数;在调用该方法后,对传出的字符串调用了JstringToCString函数。这是由于Java中所有的字符都是Unicode编码,但是在本地方法中,例如用VC编写的程序,如果没有特殊的定义一般都没有使用Unicode的编码方式。为了让本地方法能够访问Java中定义的中文字符及Java访问本地方法产生的中文字符串,定义了两个方法用来做相互转换。 i、避免在被调用的JAVA类中使用静态final成员变量,因为在C++中生成一个JAVA类的对象时,静态final成员变量不会像JAVA中new对象时那样先赋值。如果出现这种情况,在C++中调用该对象的方法时会发现该对象的静态final成员变量值全为0或者null(根据成员变量的类型而定)。 3.3调用JAVA中的静态方法 //调用静态方法 jmethodIDmid=env->GetStaticMethodID(cls,"getHelloWorld","()Ljava/lang/String;"); jstringmsg=(jstring)env->CallStaticObjectMethod(cls,mid); 3.4调用JAVA中的静态属性 jfieldIDfid=env->GetStaticFieldID(cls,"COUNT","I"); intcount=(int)env->GetStaticIntField(cls,fid); cout< 3.5调用JAVA中的带参数构造函数 //调用构造函数 jmethodIDmid=env->GetMethodID(cls," jobjectdemo=env->NewObject(cls,mid,arg); //验证是否构造成功 mid=env->GetMethodID(cls,"getMessage","()Ljava/lang/String;"); jstringmsg=(jstring)env->CallObjectMethod(demo,mid); 3.6传入传出数组 //传入传出数组 //构造数组 longarrayCpp[]={1,3,5,7,9}; jintArrayarray=env->NewIntArray(5); env->SetIntArrayRegion(array,0,5,arrayCpp); //传入数组 jmethodIDmid=env->GetMethodID(cls,"setCounts","([I)V"); env->CallVoidMethod(obj,mid,array); //获取数组 mid=env->GetMethodID(cls,"getCounts","()[I"); jintArraymsg=(jintArray)env->CallObjectMethod(obj,mid,array); intlen=env->GetArrayLength(msg); jint*elems=env->GetIntArrayElements(msg,0); cout<<"ELEMENT"< env->ReleaseIntArrayElements(msg,elems,0); 3.7异常处理由于调用了Java的方法,因此难免产生操作的异常信息,如JAVA函数返回的异常,或者调用JNI方法(如GetMethodID)时抛出的异常。这些异常没有办法通过C++本身的异常处理机制来捕捉到,但JNI可以通过一些函数来获取Java中抛出的异常信息。 //异常处理 jmethodIDmid=env->GetMethodID(cls,"throwExcp","()V"); //获取异常信息 stringexceptionInfo=""; jthrowableexcp=0; excp=env->ExceptionOccurred(); if(excp) jclasscls=env->GetObjectClass(excp); jmethodIDmid=env->GetMethodID(cls,"toString","()Ljava/lang/String;"); jstringmsg=(jstring)env->CallObjectMethod(excp,mid); out< 在Java内部,所有的字符串编码采用的是Unicode即UCS-2。Unicode是用两个字节表示每个字符的字符编码方案。Unicode有一个特性:它包括了世界上所有的字符字形。所以,各个地区的语言都可以建立与Unicode的映射关系,而Java正是利用了这一点以达到异种语言之间的转换; UTF-8是另一种不同于UCS-2/UCS-4的编码方案,其中UTF代表UCSTransformationFormat,它采用变长的方式进行编码,编码长度可以是1~3(据说理论上最长可以到6,不懂)。由于UCS-2/UCS-4编码定长的原因,编码产生的字符串会包含一些特殊的字符,如\0(即0x0,所有0~256的字符Unicode编码的第一个字节),这在有些情况下(如传输或解析时)会给我们带来一些麻烦,而且对于一般的英文字母浪费了太多的空间,此外,据说UTF-8还有Unicode所没有的纠错能力(不懂!),因此,Unicode往往只是被用作一种中间码,用于逻辑表示。关于Unicode/UTF-8的更多信息,见参考1; 当我们使用默认编码方式保存源文件时,文件内容实际上是按照我们的系统设定进行编码保存的,这个设定值即file.encoding可以通过下面的程序获得: publicclassEncoding{ System.out.println( System.getProperty("file.encoding") ); javac在不指定encoding参数时,如果区域设定不正确,则可能造成编/解码错误,这个问题在编译一个从别的环境传过来的文件时可能发生; 2、虽然在Java内部(即运行期间,Runtime)字符串是以Unicode形式存在的,但在class文件中信息是以UTF-8形式存储的(Unicode仅被用作逻辑表示中间码); 3.对于Web应用,以Tomcat为例,JSP/Servlet引擎提供的JSP转换工具(jspc)搜索JSP文件中用<%@pagecontentType="text/html;charset= 下面重点讨论JNI中在C++程序与Java程序间进行数据传递时需要注意的问题。在JNI中jstring采用的是UCS-2编码,与Java中String的编码方式一致。但是在C++中,字符串是用char(8位)或者wchar_t(16位,Unicode编码与jchar一致,但并非所有开发平台上都是Unicode编码,详见参考6),下面的程序证明了这一点(编译环境:VC6): localeloc("Chinese-simplified"); //localeloc("chs"); //localeloc("ZHI"); //localeloc(".936"); wcout.imbue(loc); wcout< wchar_twch[]={0x4E2D,0x6587,0x0}; //"中文"二字的Unicode编码 wcout< JNI提供了几个方法来实现jstring与char/wchar_t之间的转换。 jsizeGetStringLength(jstringstr) constjchar*GetStringChars(jstringstr,jboolean*isCopy)voidReleaseStringChars(jstringstr,constjchar*chars) 此外,为了便于以UTF-8方式进行传输、存储,JNI还提供了几个操作UTF格式的方法: jsizeGetStringUTFLength(jstringstr)constchar*GetStringUTFChars(jstringstr,jboolean*isCopy)voidReleaseStringUTFChars(jstringstr,constchar*chars) GetStringChars返回的是Unicode格式的编码串,而GetStringUTFChars返回的是UTF-8格式的编码串。要创建一个jstring,可以用如下方式: intlen=MultiByteToWideChar(CP_ACP,0, str,strlen(str),buffer,slen); 而要将一个jstring对象转为一个char字符串数组,可以: intJStringToChar(JNIEnv*env, jstringstr, LPTSTRdesc, intdesc_len) //Checkbuffersize if(env->GetStringLength(str)*2+1>desc_len) {return-2;} memset(desc,0,desc_len); constwchar_t*w_buffer=env->GetStringChars(str,0); len=WideCharToMultiByte(CP_ACP,0, w_buffer,wcslen(w_buffer)+1,desc,desc_len,NULL,NULL); 当然,按照上面的分析,你也可以直接将GetStringChars的返回结果作为wchar_t串来进行操作。或者,如果你愿意,你也可以将GetStringUTFChars的结果通过MultiByteToWideChar转换为UCS2编码串,再通过WideCharToMultiByte转换为多字节串。 constchar*pstr=env->GetStringUTFChars(str,false); intnLen=MultiByteToWideChar(CP_UTF8,0,pstr,-1,NULL,NULL); //得到UTF-8编码的字符串长度 LPWSTRlpwsz=newWCHAR[nLen]; MultiByteToWideChar(CP_UTF8,0,pstr,-1,lpwsz,nLen); //转换的结果是UCS2格式的编码串 intnLen1=WideCharToMultiByte(CP_ACP, 0,lpwsz,nLen, NULL,NULL,NULL,NULL); LPSTRlpsz=newCHAR[nLen1]; WideCharToMultiByte(CP_ACP,0,lpwsz,nLen,lpsz,nLen1,NULL,NULL); //将UCS2格式的编码串转换为多字节 cout<<"Out:"< delete[]lpwsz; delete[]lpsz; 当然,我相信很少有人想要或者需要这么做。这里需要注意一点,GetStringChars的返回值是jchar,而GetStringUTFChars的返回值是constchar*。 除了上面的办法外,当需要经常在jstring和char*之间进行转换时我们还有一个选择,那就是下面的这个类。这个类本来是一个叫RogerS.Reynolds的老外提供的,想法非常棒,但用起来却不太灵光,因为作者将考虑的重心放在UTF格式串上,但在实际操作中,我们往往使用的却是ACP(ANSIcodepage)串。下面是原作者的程序: classUTFString{ private:UTFString(); //Defaultctor-disallowed public: //Createanewinstancefromthespecifiedjstring UTFString(JNIEnv*env,constjstring&str): mEnv(env), mJstr(str), mUtfChars((char*)mEnv->GetStringUTFChars(mJstr,0)),mString(mUtfChars){} //Createanewinstancefromthespecifiedstring UTFString(JNIEnv*env,conststring&str): mString(str), mJstr(env->NewStringUTF(str.c_str())), mUtfChars((char*)mEnv->GetStringUTFChars(mJstr,0)){} //CreateanewinstanceasacopyofthespecifiedUTFString UTFString(constUTFString&rhs): mEnv(rhs.mEnv), mJstr(mEnv->NewStringUTF(rhs.mUtfChars)), //Deletetheinstanceandreleaseallocatedstorage ~UTFString(){ mEnv->ReleaseStringUTFChars(mJstr,mUtfChars); //assignanewvaluetothisinstancefromthegivenstringUTFString&operator=(conststring&rhs){ mJstr=mEnv->NewStringUTF(rhs.c_str()); mUtfChars=(char*)mEnv->GetStringUTFChars(mJstr,0);mString=mUtfChars; return*this; //assignanewvaluetothisinstancefromthegivenchar* UTFString&operator=(constchar*ptr){ mJstr=mEnv->NewStringUTF(ptr); //SupplyoperatormethodsforconvertingtheUTFStringtoastring //orchar*,makingiteasytopassUTFStringargumentstofunctions//thatrequirestringorchar*parameters. string&GetString(){returnmString;} operatorstring(){returnmString;} operatorconstchar*(){ returnmString.c_str(); operatorjstring(){ returnmJstr; private: JNIEnv*mEnv; //Theenviromentpointerforthisnativemethod. jstringmJstr; //AcopyofthejstringobjectthatthisUTFStringrepresentschar*mUtfChars; //PointertothedatareturnedbyGetStringUTFCharsstringmString; //stringbufferforholdingthe"value"ofthisinstance}; 我将它改了改: 下面是一个使用该类的例子(真正跟用于演示的code很少,大部分都是些routinecode,:)):packagecom.vladium.utils;