JNI函数调用大全

android开发 专栏收录该内容
22 篇文章 0 订阅

转自这里写链接内容
Chap1:JNI完全手册 3
Chap2:JNI-百度百科 11
Chap 3:javah命令帮助信息 16
Chap 4:用javah产生一个.h文件 17
Chap5:jni教程(very very good) 19
Chap6: JNI传递返回值 26
15.2.2.3 传递字符串 28
15.2.2.4 传递整型数组 29
15.2.2.5 传递字符串数组 30
15.2.2.6 传递对象数组 31
Chap7:Jni中C++和Java的参数传递 33
Chap8:如何将java传递过来的jbyteArray转换成C/C++中的BYTE数组 47
Chap5:使用JNI技术实现java程序调用第三方dll(c/c++)文件的功能 47
Chap9:如何编写jni方法(转载) 55
1、实例一:在jni中调用标准c中自带的函数printf(): 57
2、实例二、调用c 语言用户定义的函数 58
3、实例三、在jni函数中访问java类中的对象实例域 58
4、实例四:在jni函数中访问类的静态实例域 60
5、实例五:在jni函数中调用java对象的方法 60
6、实例六:在jni函数中调用java类的静态方法 61
7、实例七:jni函数中传递基本数据类型参数 62
8、实例八:在jni函数中传递对象类型参数 62
9、实例九:在jni函数中处理字符串 63
10、实例十:在jni函数中处理数组 64
11、实例十一:在jni中的返回值问题 65
12、实例十二:在jni中创建java类对象: 66
Chap10:在 Windows 中实现 Java 本地方法 66
1.Java 调用 C 67
2.调试 76
3.其他信息 79
Chap11:如何在C/C++中调用Java 80
1.环境搭建 81
2.初始化虚拟机 83
3.访问类方法 85
4访问类属性 87
5.访问构造函数 88
6.数组处理 89
7.中文处理 89
8.异常 91
9.线程和同步访问 91
10.时间 92
Chap12:基本JNI调用技术(c/c++与java互调) 93
Chap13:JNI的c代码中,另外一个线程获取 JNIEnv 96
chap 14:当JNI遇到多线程--java对象如何被C++中的多个线程访问? 97
chap 15:JNI在多线程中的应用 101
chap 16:JNI限制(多线程) 105
chap 17:使用 Java Native Interface 的最佳实践 106
1.性能缺陷 107
2.正确性缺陷 117
3.避免常见缺陷 121
4.结束语 128
Chap18:JNI设计实践之路 129
一、 前言 129
二、 JNI基础知识简介 130
三、 Java程序调用非Java程序 131
四、 C/C++访问Java成员变量和成员方法 138
五、 异常处理 140
六、 MFC程序中嵌入Java虚拟机 142
Chap19:JNI编程系列之基础篇 148
System.loadLibrary(“HelloWorld”); 149
JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *, jobject); 150
Chap20:JNI编程系列之中级篇(上) 151
1. Java基本类型的传递 151
2. String参数的传递 151
3. 数组类型的传递 153
4. 二维数组和String数组 154
Chap21:JNI编程系列之高级篇 155
1. 在一般的Java类中定义native方法 156
2. 访问Java类的域和方法 156
3. 在native方法中使用用户定义的类 157
4. 异常处理 158

注:chap1~13, JNI 函数编写教程,其中chap5讲得好;
Chap14~, JNIEnv和多线程,其中chap17讲得好。

Chap1:JNI完全手册
  最近在公司里做了一个手机的项目,需要JAVA程序在发送短信的时候和第三方的短信服务器连接。短信接口是用C++写的。琢磨了三天,大致搞懂了JNI的主体部分。先将心得整理,希望各位朋友少走弯路。
  首先引用一篇文章,介绍一个简单的JNI的调用的过程。
  JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能。解决JAVA对本地操作的一种方法就是JNI。
  JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。
  简单介绍及应用如下:
  一、JAVA中所需要做的工作
  在JAVA程序中,首先需要在类中声明所调用的库名称,如下:
  static {
  System.loadLibrary(“goodluck”);
  }

  在这里,库的扩展名字可以不用写出来,究竟是DLL还是SO,由系统自己判断。
  还需要对将要调用的方法做本地声明,关键字为native。并且只需要声明,而不需要具 体实现。如下:
  public native static void set(int i);
  public native static int get();
  然后编译该JAVA程序文件,生成CLASS,再用JAVAH命令,JNI就会生成C/C++的头文件。
  例如程序testdll.java,内容为:
  public class testdll
  {
  static
  {
  System.loadLibrary(“goodluck”);
  }
  public native static int get();
  public native static void set(int i);
  public static void main(String[] args)
  {
  testdll test = new testdll();
  test.set(10);
  System.out.println(test.get());
  }
  }

  用javac testdll.java编译它,会生成testdll.class。
  再用javah testdll,则会在当前目录下生成testdll.h文件,这个文件需要被C/C++程序调用来生成所需的库文件。
  二、C/C++中所需要做的工作
  对于已生成的.h头文件,C/C++所需要做的,就是把它的各个方法具体的实现。然后编译连接成库文件即可。再把库文件拷贝到JAVA程序的路径下面,就可以用JAVA调用C/C++所实现的功能了。
  接上例子。我们先看一下testdll.h文件的内容:
  /* DO NOT EDIT THIS FILE - it is machine generated */
  #include
  /* Header for class testdll */
  #ifndef _Included_testdll
  #define _Included_testdll
  #ifdef __cplusplus
  extern “C” {
  #endif
  /*
  * Class: testdll
  * Method: get
  * Signature: ()I
  */
  JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass);
  /*
  * Class: testdll
  * Method: set
  * Signature: (I)V
  */
  JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);
  #ifdef __cplusplus
  }
  #endif
  #endif
  在具体实现的时候,我们只关心两个函数原型
  JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass); 和
  JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);
  这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。而jint是以JNI为中介使JAVA的int类型与本地的int沟通的一种类型,我们可以视而不见,就当做int使用。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。
  好,下面我们用testdll.cpp文件具体实现这两个函数:
  #include “testdll.h”
  int i = 0;
  JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass)
  {
  return i;
  }
  JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint j)
  {
  i = j;
  }
  编译连接成库文件,本例是在WINDOWS下做的,生成的是DLL文件。并且名称要与JAVA中需要调用的一致,这里就是goodluck.dll 。把goodluck.dll拷贝到testdll.class的目录下,java testdll运行它,就可以观察到结果了。
  我的项目比较复杂,需要调用动态链接库,这样在JNI传送参数到C程序时,需要对参数进行处理转换。才可以被C程序识别。
  大体程序如下:
  public class SendSMS {
  static
  {
  System.out.println(System.getProperty(“java.library.path”));
  System.loadLibrary(“sms”);
  }
  public native static int SmsInit();
  public native static int SmsSend(byte[] mobileNo, byte[] smContent);
  }
  在这里要注意的是,path里一定要包含类库的路径,否则在程序运行时会抛出异常:
  java.lang.UnsatisfiedLinkError: no sms in java.library.path
  at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1491)
  at java.lang.Runtime.loadLibrary0(Runtime.java:788)
  at java.lang.System.loadLibrary(System.java:834)
  at com.mobilesoft.sms.mobilesoftinfo.SendSMS.(SendSMS.java:14)
  at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)
  Exception in thread “main”
  指引的路径应该到.dll文件的上一级,如果指到.dll,则会报:
  java.lang.UnsatisfiedLinkError: C:\sms.dll: Can’t find dependent libraries
  at java.lang.ClassLoader$NativeLibrary.load(Native Method)
  at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1560)
  at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1485)
  at java.lang.Runtime.loadLibrary0(Runtime.java:788)
  at java.lang.System.loadLibrary(System.java:834)
  at com.mobilesoft.sms.mobilesoftinfo.SendSMS.(SendSMS.java:14)
  at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)
  Exception in thread “main”

  通过编译,生成com_mobilesoft_sms_mobilesoftinfo_SendSMS.h头文件。(建议使用Jbuilder进行编译,操作比较简单!)这个头文件就是Java和C之间的纽带。要特别注意的是方法中传递的参数jbyteArray,这在接下来的过程中会重点介绍。
  /* DO NOT EDIT THIS FILE - it is machine generated */
  #include
  /* Header for class com_mobilesoft_sms_mobilesoftinfo_SendSMS */
  #ifndef _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS
  #define _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS
  #ifdef __cplusplus
  extern “C” {
  #endif
  /*
  * Class: com_mobilesoft_sms_mobilesoftinfo_SendSMS
  * Method: SmsInit
  * Signature: ()I
  */
  JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit
  (JNIEnv *, jclass);
  /*
  * Class: com_mobilesoft_sms_mobilesoftinfo_SendSMS
  * Method: SmsSend
  * Signature: ([B[B)I
  */
  JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend
  (JNIEnv *, jclass, jbyteArray, jbyteArray);
  #ifdef __cplusplus
  }
  #endif
  #endif

  对于我要调用的C程序的动态链接库,C程序也要提供一个头文件,sms.h。这个文件将要调用的方法罗列了出来。
  /*
  * SMS API
  * Author: yippit
  * Date: 2004.6.8
  */
  #ifndef MCS_SMS_H
  #define MCS_SMS_H
  #define DLLEXPORT __declspec(dllexport)
  /sms storage/
  #define SMS_SIM 0
  #define SMS_MT 1
  /sms states/
  #define SMS_UNREAD 0
  #define SMS_READ 1
  /sms type/
  #define SMS_NOPARSE -1
  #define SMS_NORMAL 0
  #define SMS_FLASH 1
  #define SMS_MMSNOTI 2
  typedef struct tagSmsEntry {
  int index; /index, start from 1/
  int status; /read, unread/
  int type; /-1-can’t parser 0-normal, 1-flash, 2-mms/
  int storage; /SMS_SIM, SMS_MT/
  char date[24];
  char number[32];
  char text[144];
  } SmsEntry;
  DLLEXPORT int SmsInit(void);
  DLLEXPORT int SmsSend(char *phonenum, char *content);
  DLLEXPORT int SmsSetSCA(char *sca);
  DLLEXPORT int SmsGetSCA(char *sca);
  DLLEXPORT int SmsSetInd(int ind);
  DLLEXPORT int SmsGetInd(void);
  DLLEXPORT int SmsGetInfo(int storage, int *max, int *used);
  DLLEXPORT int SmsSaveFlash(int flag);
  DLLEXPORT int SmsRead(SmsEntry *entry, int storage, int index);
  DLLEXPORT int SmsDelete(int storage, int index);
  DLLEXPORT int SmsModifyStatus(int storage, int index); /unread -> read/
  #endif

  在有了这两个头文件之后,就可以进行C程序的编写了。也就是实现对JNI调用的两个方法。在网上的资料中,由于调用的方法实现的都比较简单,(大多是打印字符串等)所以避开了JNI中最麻烦的部分,也是最关键的部分,参数的传递。由于Java和C的编码是不同的,所以传递的参数是要进行再处理,否则C程序是会对参数在编译过程中提出警告,例如;warning C4024: ‘SmsSend’ : different types for formal and actual parameter 2等。
  Sms.c的程序如下:
  #include “sms.h”
  #include “com_mobilesoft_sms_mobilesoftinfo_SendSMS.h”
  JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit(JNIEnv * env, jclass jobject)
  {
  return SmsInit();
  }

  JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend(JNIEnv * env, jclass jobject, jbyteArray mobileno, jbyteArray smscontent)
  {
  char * pSmscontent ;
  //jsize theArrayLengthJ = (*env)->GetArrayLength(env,mobileno);
  jbyte * arrayBody = (*env)->GetByteArrayElements(env,mobileno,0);
  char * pMobileNo = (char *)arrayBody;
  printf(“[%s]\n “, pMobileNo);
  //jsize size = (*env)->GetArrayLength(env,smscontent);
  arrayBody = (*env)->GetByteArrayElements(env,smscontent,0);
  pSmscontent = (char *)arrayBody;
  printf(“

Chap2:JNI-百度百科

目录
定义
设计目的
书写步骤
简要使用例子
调用中考虑的问题
对JAVA传入数据的处理

定义
  JNI是Java Native Interface的缩写,中文为JAVA本地调用。从Java 1.1开始,Java Native Interface (JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。
  使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的,比如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少保证本地代码能工作在任何Java 虚拟机实现下。
设计目的
  ·标准的java类库可能不支持你的程序所需的特性。
  ·或许你已经有了一个用其他语言写成的库或程序,而你希望在java程序中使用它。
·你可能需要用底层语言实现一个小型的时间敏感代码,比如汇编,然后在你的java程序中调用这些功能。

书写步骤
  ·编写带有native声明的方法的java类
  ·使用 javac 命令编译所编写的java类
  ·使用 “ javah -jni java类名” 生成扩展名为h的头文件
  ·使用C/C++实现本地方法
  ·将C/C++编写的文件生成动态连接库
  ·ok
  1) 编写java程序:这里以HelloWorld为例。
  代码1:
  class HelloWorld {
  public native void displayHelloWorld();
  static {
  System.loadLibrary(“hello”);
  }
  public static void main(String[] args) {
  new HelloWorld().displayHelloWorld();
  }
  }
  声明native方法:如果你想将一个方法做为一个本地方法的话,那么你就必须声明改方法为native的,并且不能实现。其中方法的参数和返回值在后面讲述。 Load动态库:System.loadLibrary(“hello”);加载动态库(我们可以这样理解:我们的方法 displayHelloWorld()没有实现,但是我们在下面就直接使用了,所以必须在使用之前对它进行初始化)这里一般是以static块进行加载的。同时需要注意的是System.loadLibrary();的参数“hello”是动态库的名字。
  2) 编译
  没有什么好说的了 javac HelloWorld.java
3) 生成扩展名为h的头文件
javah -jni HelloWorld
头文件的内容:
/* DO NOT EDIT THIS FILE - it is machine generated */
  1. include
  /* Header for class HelloWorld */
  1. ifndef _Included_HelloWorld
  2. define _Included_HelloWorld
  3. ifdef __cplusplus
  extern “C” {
  1. endif
  /*
  * Class: HelloWorld
  * Method: displayHelloWorld
  * Signature: ()V
  * /
  JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld (JNIEnv *, jobject);
  1. ifdef __cplusplus
  }
  1. endif
  2. endif
  (这里我们可以这样理解:这个h文件相当于我们在java里面的接口,这里声明了一个 Java_HelloWorld_displayHelloWorld (JNIEnv *, jobject);方法,然后在我们的本地方法里面实现这个方法,也就是说我们在编写C/C++程序的时候所使用的方法名必须和这里的一致)。
  4) 编写本地方法实现和由javah命令生成的头文件里面声明的方法名相同的方法。
  代码2:
  1 #include “jni.h”
  2 #include “HelloWorld.h”
  3 //#include other headers
  4 JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj)
  {
  printf(“Hello world!\n”);
  return;
  }
  注意代码2中的第1行,需要将jni.h(该文件可以在%JAVA_HOME%/include文件夹下面找到)文件引入,因为在程序中的JNIEnv、 jobject等类型都是在该头文件中定义的;另外在第2行需要将HelloWorld.h头文件引入(我是这么理解的:相当于我们在编写java程序的时候,实现一个接口的话需要声明才可以,这里就是将HelloWorld.h头文件里面声明的方法加以实现。当然不一定是这样)。然后保存为 HelloWorldImpl.c就ok了。
  5) 生成动态库
  这里以在Windows中为例,需要生成dll文件。在保存HelloWorldImpl.c文件夹下面,使用VC的编译器cl成。 cl -I%java_home%\include -I%java_home%\include\win32 -LD HelloWorldImp.c -Fehello.dll 注意:生成的dll文件名在选项-Fe后面配置,这里是hello,因为在HelloWorld.java文件中我们loadLibary的时候使用的名字是hello。当然这里修改之后那里也需要修改。另外需要将-I%java_home%\include -I%java_home%\include\win32参数加上,因为在第四步里面编写本地方法的时候引入了jni.h文件。
6) 运行程序 java HelloWorld就ok.

简要使用例子
  下面是一个简单的例子实现打印一句话的功能,但是用的c的printf最终实现。一般提供给java的jni接口包括一个so文件(封装了c函数的实现)和一个java文件(需要调用path的类)。
  1. JNI的目的是使java方法中能够调用c实现的一些函数,比如以下的java类,就需要调用一个本地函数testjni(一般声明为private native类型),首先需要创建文件weiqiong.java,内容如下:
class weiqiong {
static { System.loadLibrary(“testjni”);//载入静态库,test函数在其中实现
}

private native void testjni(); //声明本地调用
public void test()
{
testjni();
}
public static void main(String args[])
{
weiqiong haha = new weiqiong(); haha.test();
}
}
  2.然后执行javac weiqiong.java,如果没有报错,会生成一个weiqiong.class。
  3.然后设置classpath为你当前的工作目录,如直接输入命令行:set classpath = weiqiong.class所在的完整目录(如 c:\test)再执行javah weiqiong,会生成一个文件weiqiong.h文件,其中有一个函数的声明如下:
  JNIEXPORT void JNICALL Java_weiqiong_testjni (JNIEnv *, jobject);
  4.创建文件testjni.c将上面那个函数实现,内容如下:
  1. include
  2. include
  JNIEXPORT void JNICALL Java_weiqiong_testjni (JNIEnv *env, jobject obj) { printf(“haha———go into c!!!\n”); }
  5.为了生成.so文件,创建makefile文件如下:
  libtestjni.so:testjni.o makefile gcc -Wall -rdynamic -shared -o libtestjni.so testjni.o testjni.o:testjni.c weiqiong.h gcc -Wall -c testjni.c -I./ -I/usr/java/j2sdk1.4.0/include -I/usr/java/j2sdk1.4.0/include/linux cl: rm -rf .o .so 注意:gcc前面是tab空,j2sdk的目录根据自己装的j2sdk的具体版本来写,生成的so文件的名字必须是loadLibrary的参数名前加“lib”。
  6.export LD_LIBRARY_PATH=.,由此设置library路径为当前目录,这样java文件才能找到so文件。一般的做法是将so文件copy到本机的LD_LIBRARY_PATH目录下。
7.执行java weiqiong,打印出结果:“haha———go into c!!!”

调用中考虑的问题
  在首次使用JNI的时候有些疑问,后来在使用中一一解决,下面就是这些问题的备忘:
  1。 java和c是如何互通的?
  其实不能互通的原因主要是数据类型的问题,jni解决了这个问题,例如那个c文件中的jstring数据类型就是java传入的String对象,经过jni函数的转化就能成为c的char*。
  对应数据类型关系如下表:
  Java 类型 本地c类型 说明 boolean jboolean 无符号,8 位 byte jbyte 无符号,8 位 char jchar 无符号,16 位 short jshort 有符号,16 位 int jint 有符号,32 位 long jlong 有符号,64 位 float jfloat 32 位 double jdouble 64 位 void void N/A
  JNI 还包含了很多对应于不同 Java 对象的引用类型如下图:
  2. 如何将java传入的String参数转换为c的char*,然后使用?
  java传入的String参数,在c文件中被jni转换为jstring的数据类型,在c文件中声明char* test,然后test = (char*)(*env)->GetStringUTFChars(env, jstring, NULL);注意:test使用完后,通知虚拟机平台相关代码无需再访问:(*env)->ReleaseStringUTFChars(env, jstring, test);
  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中,函数直接return bytearray就可以了。
  一种是return错误号,数据作为参数传出,但是java的基本数据类型是传值,对象是传递的引用,所以将这个需要传出的byte数组用某个类包一下,如下:
class RetObj { public byte[] bytearray; } 这个对象作为函数的参数retobj传出,通过如下函数将retobj中的byte数组赋值便于传出。代码如下:

jclass cls;
jfieldID fid;
jbyteArray bytearray;
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);

Chap 3:javah命令帮助信息
D:\Program Files\Java\jdk1.6.0_12\bin>javah
用法:javah [选项] <类>

其中 [选项] 包括:

    -help                 输出此帮助消息并退出
    -classpath <路径>     用于装入类的路径
    -bootclasspath <路径> 用于装入引导类的路径
    -d <目录>             输出目录
    -o <文件>             输出文件(只能使用 -d 或 -o 中的一个)
    -jni                  生成 JNI样式的头文件(默认)
    -version              输出版本信息
    -verbose              启用详细输出

Chap 4:用javah产生一个.h文件
2009-07-29 15:21 阅读23 评论0

Java不是完善的,Java的不足除了体现在运行速度上要比传统的C++慢许多之外,Java无法直接造访到操作体系底层(如系统硬件等),为此 Java使用native法子来扩大Java程序的功效。   可以将native法子比作Java程序同C程序的接口,其实现步骤:
  1、在Java中声明native()方式,然后编译;
  2、用javah发生一个.h文件;
  3、写一个.cpp文件实现native导出方式,其中须要包括第二步发生的.h文件(注意其中又包孕了JDK带的jni.h文件),成人聊天室;
  4、将第三步的.cpp文件编译成动态链接库文件;
  5、在Java中用System.loadLibrary()法子加载第四步发生的动态链接库文件,这个native()办法就可以在Java中被拜访了。
  JAVA本地办法实用的情形
  1.为了使用底层的主机平台的某个特性,而这个特性不能通过JAVA API拜访
  2.为了拜访一个老的体系或者使用一个已有的库,而这个体系或这个库不是用JAVA编写的
  3.为了加快程序的性能,而将一段时光敏感的代码作为本地方式实现。
  首先写好JAVA文件
/*
 * Created on 2005-12-19 Author shaoqi
 */
package com.hode.hodeframework.modelupdate,视频聊天网站;
public class CheckFile
{
   public native void displayHelloWorld();
   static
   {
 System.loadLibrary(“test”);
   }
   public static void main(String[] args) {
    new CheckFile().displayHelloWorld(); 
   }
}

然后依据写好的文件编译成CLASS文件
   然后在classes或bin之类的class根目录下(其中有已经生成的*.class文件)执行javah -jni com.hode.hodeframework.modelupdate.CheckFile,就会在class根目录下得到一个 com_hode_hodeframework_modelupdate_CheckFile.h的文件
然后依据头文件的内容编写com_hode_hodeframework_modelupdate_CheckFile.c文件

include “CheckFile.h”

include 

include 

JNIEXPORT void JNICALL Java_com_hode_hodeframework_modelupdate_CheckFile_displayHelloWorld(
JNIEnv *env, jobject obj)
{
   printf(“Hello world!
“);
   return;
}

之后编译生成DLL文件如“test.dll”,名称与System.loadLibrary(“test”)中的名称一致
  vc的编译办法:cl -I%java_home%include -I%java_home%includewin32 -LD com_hode_hodeframework_modelupdate_CheckFile.c -Fetest.dll
  最后在运行时加参数-Djava.library.path=[dll寄存的路径]

Chap5:jni教程(very very good)
本文来源:http://blog.csdn.net/sunjavaduke/archive/2007/07/28/1713895.aspx

本教程摘自IBM DW,如有转载,请声明!
Java 本机接口(Java Native Interface (JNI))是一个本机编程接口,它是 Java 软件开发工具箱(Java Software Development Kit (SDK))的一部分。

JNI 允许 Java 代码使用以其它语言(譬如 C 和 C++)编写的代码和代码库。Invocation API(JNI 的一部分)可以用来将 Java 虚拟机(JVM)嵌入到本机应用程序中,从而允许程序员从本机代码内部调用 Java 代码。
本教程涉及 JNI 最常见的两个应用:从 Java 程序调用 C/C++,以及从 C/C++ 程序调用 Java 代码。我们将讨论 Java 本机接口的这两个基本部分以及可能出现的一些更高级的编程难题。
本教程将带您去了解使用 Java 本机接口的所有步骤。您将学习如何从 Java 应用程序内部调用本机 C/C++ 代码以及如何从本机 C/C++ 应用程序内部调用 Java 代码。
所有示例都是使用 Java、C 和 C++ 代码编写的,并可以移植到 Windows 和基于 UNIX 的平台上。要完全理解这些示例,您必须有一些 Java 语言编程经验。此外,您还需要一些 C 或 C++ 编程经验。严格来说,JNI 解决方案可以分成 Java 编程任务和 C/C++ 编程任务,由不同的程序员完成每项任务。然而,要完全理解 JNI 是如何在两种编程环境中工作的,您必须能够理解 Java 和 C/C++ 代码。
我们还将讲述一些高级主题,包括本机方法的异常处理和多线程。要充分理解本教程,您应该熟悉 Java 平台的安全性模型,并有一些多线程应用程序开发的经验。
这里将关于高级主题的节从较基本的循序渐进 JNI 简介中划分出来。现在,初级 Java 程序员可以先学习本教程的前两部分,掌握之后再开始学习高级主题。
要运行本教程中的示例,您需要下列工具与组件:
Java 编译器:随 SDK 一起提供的 javac.exe。
Java 虚拟机(JVM):随 SDK 一起提供的 java.exe。
本机方法 C 文件生成器:随 SDK 一起提供的 javah.exe。
定义 JNI 的库文件和本机头文件。jni.h C 头文件、jvm.lib 和 jvm.dll 或 jvm.so 文件,这些文件都是随 SDK 一起提供的。
能够创建共享库的 C 和 C++ 编译器。最常见的两个 C 编译器是用于 Windows 的 Visual C++ 和用于基于 UNIX 系统的 cc。
虽然您可以使用自己喜欢的任何开发环境,但我们将在本教程中使用示例是用随 SDK 一起提供的标准工具和组件编写的。请参阅参考资料来下载 SDK、完整的源文件以及对于完成本教程不可缺少的其它工具。本教程具体地解释了 Sun 的 JNI 实现,该实现被认为是 JNI 解决方案的标准。本教程中没有讨论其它 JNI 实现的详细信息。
在 Java 2 SDK 中,JVM 和运行时支持位于名为 jvm.dll(Windows)或 libjvm.so(UNIX)的共享库文件中。在 Java 1.1 JDK 中,JVM 和运行时支持位于名为 javai.dll(Windows)或 libjava.so(UNIX)的共享库文件中。版本 1.1 的共享库包含运行时以及类库的一些本机方法,但在版本 1.2 中已经不包含运行时,并且本机方法被放在 java.dll 和 libjava.so 中。对于以下 Java 代码,这一变化很重要:
代码是用非 JNI 本机方法编写的(因为使用了 JDK 1.0 中旧的本机方法接口)
通过 JNI Invocation 接口使用了嵌入式 JVM
在两种情况下,在您的本机库能与版本 1.2 一起使用之前,都必须重新链接它们。注:这个变化应该不影响 JNI 程序员实现本机方法 — 只有通过 Invocation API调用 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 程序访问旧代码或代码库。
需要标准 Java 类库中不支持的依赖于平台的特性。
从 Java 代码调用 C/C++ 的六个步骤
从 Java 程序调用 C 或 C ++ 代码的过程由六个步骤组成。我们将在下面几页中深入讨论每个步骤,但还是先让我们迅速地浏览一下它们。
1.编写 Java 代码。我们将从编写 Java 类开始,这些类执行三个任务:声明将要调用的本机方法;装入包含本机代码的共享库;然后调用该本机方法。
2.编译 Java 代码。在使用 Java 类之前,必须成功地将它们编译成字节码。
3.创建 C/C++ 头文件。C/C++ 头文件将声明想要调用的本机函数说明。然后,这个头文件与 C/C++ 函数实现(请参阅步骤 4)一起来创建共享库(请参阅步骤 5)。
4.编写 C/C++ 代码。这一步实现 C 或 C++ 源代码文件中的函数。C/C++ 源文件必须包含步骤 3 中创建的头文件。
5.创建共享库文件。从步骤 4 中创建的 C 源代码文件来创建共享库文件。
6.运行 Java 程序。运行该代码,并查看它是否有用。我们还将讨论一些用于解决常见错误的技巧。
步骤 1:编写 Java 代码
我们从编写 Java 源代码文件开始,它将声明本机方法(或方法),装入包含本机代码的共享库,然后实际调用本机方法。
这里是名为 Sample1.java 的 Java 源代码文件的示例:
package com.ibm.course.jni;
public class Sample1 {
public native int intMethod(int n);
public native boolean booleanMethod(boolean bool);
public native String stringMethod(String text);
public native int intArrayMethod(int[] intArray);

public static void main(String[] args) {
   System.loadLibrary("Sample1");
   Sample1 sample = new Sample1();
   int square = sample.intMethod(5);
   boolean bool = sample.booleanMethod(true);
   String text = sample.stringMethod("JAVA");
   int sum = sample.intArrayMethod(new int[] { 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);
}

}
这段代码做了些什么?
首先,请注意对 native 关键字的使用,它只能随方法一起使用。native 关键字告诉 Java 编译器:方法是用 Java 类之外的本机代码实现的,但其声明却在 Java 中。只能在 Java 类中声明本机方法,而不能实现它(但是不能声明为抽象的方法,使用native关键字即可),所以本机方法不能拥有方法主体。
现在,让我们逐行研究一下代码:
从第 3 行到第 6 行,我们声明了四个 native 方法。
在第 10 行,我们装入了包含这些本机方法的实现的共享库文件。(到步骤 5 时,我们将创建该共享库文件。)
最终,从第 12 行到第 15 行,我们调用了本机方法。注:这个操作和调用非本机 Java 方法的操作没有差异。
注:基于 UNIX 的平台上的共享库文件通常含有前缀“lib”。在本例中,第 10 行可能是 System.loadLibrary(“libSample1”);。请一定要注意您在步骤 5:创建共享库文件中生成的共享库文件名。
步骤 2:编译 Java 代码
接下来,我们需要将 Java 代码编译成字节码。完成这一步的方法之一是使用随 SDK 一起提供的 Java 编译器 javac。用来将 Java 代码编译成字节码的命令是:

C:\eclipse\workspace\IBMJNI\src\com\ibm\course\jni>javac Sample1.java
步骤 3:创建 C/C++ 头文件
第三步是创建 C/C++ 头文件,它定义本机函数说明。完成这一步的方法之一是使用 javah.exe,它是随 SDK 一起提供的本机方法 C 存根生成器工具。这个工具被设计成用来创建头文件,该头文件为在 Java 源代码文件中所找到的每个 native 方法定义 C 风格的函数。这里使用的命令是:
C:\eclipse\workspace\IBMJNI\bin>javah –classpath ./ –jni com.ibm.course.jni.Sample1
javah工具帮助
Usage: javah [options]
where [options] include:
-help Print this help message and exit
-classpath Path from which to load classes
-bootclasspath Path from which to load bootstrap classes
-d

Output directory
-o Output file (only one of -d or -o may be used)
-jni Generate JNI-style header file (default)
-version Print version information
-verbose Enable verbose output
-force Always write output files
are specified with their fully qualified names (for
instance, java.lang.Object).
在 Sample1.java 上运行 javah.exe 的结果
下面的 Sample1.h 是对我们的 Java 代码运行 javah 工具所生成的 C/C++ 头文件:
/* DO NOT EDIT THIS FILE - it is machine generated */

include

ifndef _Included_com_ibm_course_jni_Sample1

define _Included_com_ibm_course_jni_Sample1

ifdef __cplusplus

extern “C” {

endif

/*
* Class: com_ibm_course_jni_Sample1
* Method: intMethod
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_com_ibm_course_jni_Sample1_intMethod
(JNIEnv *, jobject, jint);
/*
* Class: com_ibm_course_jni_Sample1
* Method: booleanMethod
* Signature: (Z)Z
*/
JNIEXPORT jboolean JNICALL Java_com_ibm_course_jni_Sample1_booleanMethod
(JNIEnv *, jobject, jboolean);
/*
* Class: com_ibm_course_jni_Sample1
* Method: stringMethod
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_ibm_course_jni_Sample1_stringMethod
(JNIEnv *, jobject, jstring);
/*
* Class: com_ibm_course_jni_Sample1
* Method: intArrayMethod
* Signature: ([I)I
*/
JNIEXPORT jint JNICALL Java_com_ibm_course_jni_Sample1_intArrayMethod
(JNIEnv *, jobject, jintArray);

ifdef __cplusplus

}

endif

endif

关于 C/C++ 头文件
正如您可能已经注意到的那样,Sample1.h 中的 C/C++ 函数说明和 Sample1.java 中的 Java native 方法声明有很大差异。JNIEXPORT 和 JNICALL 是用于导出函数的、依赖于编译器的指示符。返回类型是映射到 Java 类型的 C/C++ 类型。附录 A:JNI 类型中完整地说明了这些类型。
除了 Java 声明中的一般参数以外,所有这些函数的参数表中都有一个指向 JNIEnv 和 jobject 的指针。指向 JNIEnv 的指针实际上是一个指向函数指针表的指针。正如将要在步骤 4 中看到的,这些函数提供各种用来在 C 和 C++ 中操作 Java 数据的能力。
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 数组类型是如何映射到 JNI C 数组类型的。
这里是一棵对象树,它显示了 JNI 伪类是如何相关的。
步骤 4:编写 C/C++ 代码
当谈到编写 C/C++ 函数实现时,有一点需要牢记:说明必须和 Sample1.h 的函数声明完全一样。我们将研究用于 C 实现和 C++ 实现的完整代码,然后讨论两者之间的差异。
C函数实现
以下是 Sample1.c,它是用 C 编写的实现:
#include “com_ibm_course_jni_Sample1.h”
#include

include “com_ibm_course_jni_Sample1.h”

include

include “stdafx.h”

include

include

include “jni.h”

include “jni_md.h”

include “./head/Base.h”

include “head/wmi.h”

include “head/com_sundy_jnidemo_ChangeMethodFromJni.h” //通过javah –jni javactransfer 生成

include

include “stdlib.h”

include “string.h”

pragma comment (lib,”BaseInfo.lib”)

pragma comment (lib,”jvm.lib”)

//硬盘信息
struct {
char name[256];
int serial;
}DiskInfo;

/**//*BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
LPTSTR strName = new CHAR[256] ;
(*GetHostName)(strName);
printf(“%s\n”,strName);
delete [] strName;
return TRUE;
}*/
//将jstring类型转换成windows类型
char* jstringToWindows( JNIEnv *env, jstring jstr );
//将windows类型转换成jstring类型
jstring WindowsTojstring( JNIEnv* env, char* str );
//主函数
BOOL WINAPI DllMain(HANDLE hHandle, DWORD dwReason, LPVOID lpReserved)
{
return TRUE;
}
//输入常用的数值类型 Boolean,Byte,Char,Short,Int,Float,Double
JNIEXPORT void JNICALL Java_com_sundy_jnidemo_ChangeMethodFromJni_displayParms
(JNIEnv *env, jobject obj, jstring s, jint i, jboolean b)
{
const char* 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 );
}
//调用一个静态方法,只有一个简单类型输出
JNIEXPORT jint JNICALL Java_com_sundy_jnidemo_ChangeMethodFromJni_add
(JNIEnv *env, jobject, jint a, jint b)
{
int rtn = (int)(a + b);
return (jint)rtn;
}
/**/输入一个数组,这里输入的是一个Boolean类型的数组
JNIEXPORT void JNICALL Java_com_sundy_jnidemo_ChangeMethodFromJni_setArray
(JNIEnv *env, jobject, jbooleanArray ba)
{
jboolean* pba = (env)->GetBooleanArrayElements(ba, 0 );
jsize len = (env)->GetArrayLength(ba);
int i=0;
// change even array elements
for( i=0; i < len; i+=2 )
{
pba[i] = JNI_FALSE;
printf( “boolean = %s\n”, (pba[i]==JNI_TRUE ? “true” : “false”) );
}
(env)->ReleaseBooleanArrayElements(ba, pba, 0 );
}
/**/返回一个字符串数组
JNIEXPORT jobjectArray JNICALL Java_com_sundy_jnidemo_ChangeMethodFromJni_getStringArray
(JNIEnv *env, jobject)
{
jstring str;
jobjectArray args = 0;
jsize len = 5;
char* sa[] = { “Hello,”, “world!”, “JNI”, “is”, “fun” };
int i=0;
args = (env)->NewObjectArray(len,(env)->FindClass(“java/lang/String”),0);
for( i=0; i < len; i++ )
{
str = (env)->NewStringUTF(sa[i] );
(env)->SetObjectArrayElement(args, i, str);
}
return args;
}
//返回一个结构,这里返回一个硬盘信息的简单结构类型
JNIEXPORT jobject JNICALL Java_com_sundy_jnidemo_ChangeMethodFromJni_getStruct
(JNIEnv *env, jobject obj)
{
/*// 下面为获取到Java中对应的实例类中的变量*/
//获取Java中的实例类
jclass objectClass = (env)->FindClass(“com/sundy/jnidemo/DiskInfo”);
//获取类中每一个变量的定义
//名字
jfieldID str = (env)->GetFieldID(objectClass,”name”,”Ljava/lang/String;”);
//序列号
jfieldID ival = (env)->GetFieldID(objectClass,”serial”,”I”);

//给每一个实例的变量付值
(env)->SetObjectField(obj,str,(env)->NewStringUTF("my name is D:"));
(env)->SetShortField(obj,ival,10);

return obj;

}
//返回一个结构数组,返回一个硬盘信息的结构数组
JNIEXPORT jobjectArray JNICALL Java_com_sundy_jnidemo_ChangeMethodFromJni_getStructArray
(JNIEnv *env, jobject _obj)
{
//申明一个object数组
jobjectArray args = 0;

//数组大小
jsize        len = 5;
//获取object所属类,一般为ava/lang/Object就可以了
jclass objClass = (env)->FindClass("java/lang/Object");
//新建object数组
args = (env)->NewObjectArray(len, objClass, 0);
/**//* 下面为获取到Java中对应的实例类中的变量*/
//获取Java中的实例类
jclass objectClass = (env)->FindClass("com/sundy/jnidemo/DiskInfo");

//获取类中每一个变量的定义
//名字
jfieldID str = (env)->GetFieldID(objectClass,"name","Ljava/lang/String;");
//序列号
jfieldID ival = (env)->GetFieldID(objectClass,"serial","I");
//给每一个实例的变量付值,并且将实例作为一个object,添加到objcet数组中
for(int i=0; i < len; i++ )
{
    //给每一个实例的变量付值
    jstring jstr = WindowsTojstring(env,"我的磁盘名字是 D:");
    //(env)->SetObjectField(_obj,str,(env)->NewStringUTF("my name is D:"));
    (env)->SetObjectField(_obj,str,jstr);
    (env)->SetShortField(_obj,ival,10);
    //添加到objcet数组中
    (env)->SetObjectArrayElement(args, i, _obj);
}
//返回object数组
return args;

}
//将jstring类型转换成windows类型
char* jstringToWindows( JNIEnv *env, jstring jstr )
{
int length = (env)->GetStringLength(jstr );
const jchar* jcstr = (env)->GetStringChars(jstr, 0 );
char* rtn = (char*)malloc( length*2+1 );
int size = 0;
size = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR)jcstr, length, rtn,(length*2+1), NULL, NULL );
if( size <= 0 )
return NULL;
(env)->ReleaseStringChars(jstr, jcstr );
rtn[size] = 0;
return rtn;
}
//将windows类型转换成jstring类型
jstring WindowsTojstring( JNIEnv* env, char* str )
{
jstring rtn = 0;
int slen = strlen(str);
unsigned short * buffer = 0;
if( slen == 0 )
rtn = (env)->NewStringUTF(str );
else
{
int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, NULL, 0 );
buffer = (unsigned short *)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 );
return rtn;
}
Java 测试native代码
这没有什么多说的,看代码吧
//主测试程序
public static void main(String[] args) {
ChangeMethodFromJni changeJni = new ChangeMethodFromJni();
//输入常用的数值类型(string int boolean)
System.out
.println(“——————输入常用的数值类型(string int boolean)———–”);
changeJni.displayParms(“Hello World!”, 100, true);
//调用一个静态方法
System.out.println(“——————调用一个静态方法———–”);
int ret = changeJni.add(12, 20);
System.out.println(“The result is: ” + String.valueOf(ret));
//输入一个数组
System.out.println(“——————输入一个数组———–”);
boolean[] blList = new boolean[] { true, false, true };
changeJni.setArray(blList);
//返回一个字符串数组
System.out.println(“——————返回一个字符串数组———–”);
String[] strList = changeJni.getStringArray();
for (int i = 0; i < strList.length; i++) {
System.out.print(strList[i]);
}
System.out.println();
System.out.println(“——————返回一个结构———–”);
//返回一个结构
DiskInfo disk = changeJni.getStruct();
System.out.println(“name:” + disk.name);
System.out.println(“Serial:” + disk.serial);
//返回一个结构数组
System.out.println(“——————返回一个结构数组 ———–”);
DiskInfo[] diskList = changeJni.getStructArray();
for (int i = 0; i < diskList.length; i++) {
System.out.println(“name:” + diskList[i].name);
System.out.println(“Serial:” + diskList[i].serial);
}
}
注:本程序在VS2003,eclipse (jse5.0) winxp sp2编译通过

posted on 2005-05-02 20:22 sundy 阅读(4406) 评论(21) 编辑 收藏 所属分类: Java

评论

re: Jni中C++和Java的参数传递 2005-05-22 14:35 张磊

请问如果想返回byte[]类型该怎么做 回复 更多评论

re: Jni中C++和Java的参数传递 2005-05-23 08:37 sundy

因为:
byte[] jbyteArray 比特型数组
所以你将byte[] 作为一个jbyteArray数组传递就可以了
回复 更多评论

re: Jni中C++和Java的参数传递 2005-09-21 14:46 小影

请问如果我想把在C++里面计算好的一个二维数组传回给java程序接受,该怎么写代码呢?我找了很多这方面的书和资料,都没有关于传递二维数组的介绍,请您给予指导,多谢啦^_^ 回复 更多评论

re: Jni中C++和Java的参数传递 2005-09-21 17:47 sundy

我没有直接传递过二维数组
但我想你可以把试一试二维数组转换成为一个Hashmap的数组传出来。
请参考”如何在Jni中传递出Hashmap的数组?”的一些代码
回复 更多评论

re: Jni中C++和Java的参数传递 2005-12-26 16:32 wangjian

返回一个结构数组时,为什么每个对象的数据都是一样的?即5个Diskinfo的成员值都相同,能不能不相同? 回复 更多评论

re: Jni中C++和Java的参数传递 2005-12-26 16:55 wangjian

我把5个DiskInfo对象的成员serial分别设置为1、2、3、4、5,可是传递到java后5个对象的serial成员值都是5,为什么这样阿?盼回复,多谢! 回复 更多评论

re: Jni中C++和Java的参数传递 2005-12-27 21:51 sundy

//给每一个实例的变量付值,并且将实例作为一个object,添加到objcet数组中
for(int i=0; i < len; i++ )
{
……
//添加到objcet数组中
(env)->SetObjectArrayElement(args, i, _obj);
}
你看看设置的_Obj是不是都是同一个??
回复 更多评论

re: Jni中C++和Java的参数传递 2005-12-28 13:32 wangjian

如下所示,我就是把你程序中(env)->SetShortField(_obj,ival,10)的参数10换成i,结果每个对象都是对象的serial成员值都是4,请问怎样实现多个不同对象的传递?
for(int i=0; i < len; i++ )
{
jstring jstr = WindowsTojstring(env,”我的磁盘名字是D:”);
(env)->SetObjectField(_obj,str,jstr);
(env)->SetShortField(_obj,ival,i);
(env)->SetObjectArrayElement(args, i, _obj);
}
回复 更多评论

re: Jni中C++和Java的参数传递 2005-12-28 15:15 sundy

应该没有问题的呀,
SetObjectArrayElement的时候,_obj是不同的吗?
要不你将for循环改为:
jstring jstr = WindowsTojstring(env,”我的磁盘名字是C:”);
(env)->SetObjectField(_obj,str,jstr);
(env)->SetShortField(_obj,ival,0);
(env)->SetObjectArrayElement(args, 0, _obj);
jstring jstr = WindowsTojstring(env,”我的磁盘名字是D:”);
(env)->SetObjectField(_obj,str,jstr);
(env)->SetShortField(_obj,ival,1);
(env)->SetObjectArrayElement(args, 1, _obj);
看看对吗? 回复 更多评论

re: Jni中C++和Java的参数传递 2005-12-29 20:42 wangjian

这样作不对,不过我找到正确的方法了,要用构造函数生成新的对象。 回复 更多评论

re: Jni中C++和Java的参数传递 2006-01-17 11:07 luli

SQLRETURN SQLAllocHandle( SQLSMALLINT HandleType,
SQLHANDLE InputHandle,
SQLHANDLE * OutputHandlePtr
)
这是odbc api里的一个函数 SQLHANDLE 是一个结构
c#里的引用方式如下
[DllImport(“ODBC32.dll”)]
private static extern short SQLAllocHandle(short hType, IntPtr inputHandle, out IntPtr outputHandle);
但我不清楚 SQLHANDLE 结构具体怎么构造的 因此我无法用java类来模拟
我是菜鸟 望解答 谢过了 回复 更多评论

re: Jni中C++和Java的参数传递 2006-01-17 14:25 luli

忘了补充 SQLHANDLE InputHandle与SQLHANDLE * OutputHandlePtr
一个是结构 一个是结构指针 那我是否该如下模拟
class SQLHANDLE
{
}
public class test
{
SQLHANDLE a=new SQLHANDLE ();
public static void main(String args[]) {
int i=SQLAllocHandle( SQLSMALLINT HandleType, new SQLHANDLE(),a)
}
}
回复 更多评论

re: Jni中C++和Java的参数传递 2006-03-21 17:31 Hefe

WideCharToMultiByte();
MultiByteToWideChar();
请问这两个函数实现什么功能,请作者给出代码,多谢!

回复 更多评论

re: Jni中C++和Java的参数传递 2006-03-22 08:47 sundy

@Hefe look here: http://www.google.com/search?hl=zh-CN&lr=lang_zh-CN&q=WideCharToMultiByte
回复 更多评论

re: Jni中C++和Java的参数传递 2006-03-28 17:40 dijk

要在c函数中调用java类的类成员的方法,比如调用JEditorPane类型成员的setText方法,该怎么办? 回复 更多评论

re: Jni中C++和Java的参数传递 2006-04-16 21:33 陈世雄

java中函数的处理中,对于对象类型(非基本类型int,long…)的输入参数,函数中是可以修改输入参数的内容,函数执行完毕,修改仍然是有效的。
jni中 是否也是这样呢?
回复 更多评论

re: Jni中C++和Java的参数传递 2006-04-18 17:50 王文波

你好:
向你请教一个问题:我想用jini来调用dll。我在jbuilder中新建的简单的project调用jini运行正常。但是,我现在要对一个工程软件进行二次开发,该软件的
开发也使用jbuilder生成一个project,然后放在指定的路径下就可以了,该软件在运行的时候会自动读取该project。我在这个软件二次开发的project中使用
jini,则总是报错:unsatisfiedlinkError get()。其中get()方法名。请问该怎么解决这个问题?
我的邮箱:zwj23232@tom.com 回复 更多评论

re: Jni中C++和Java的参数传递 2006-05-29 21:25 single

re: Jni中C++和Java的参数传递 2005-12-29 20:42 wangjian

这样作不对,不过我找到正确的方法了,要用构造函数生成新的对象。 回复

能说说方法吗? 回复 更多评论

re: Jni中C++和Java的参数传递 2006-08-29 11:34 yangyongfa

我正在做JNI,是在C++中调用JAVA类的方法,请问,我在JAVA类的方法中参数使 用的是byte[],而我在C++中是把一个文件读成unsigned char*,请问怎么可以正确调用JAVA中的方法? 类中方法原型:public boolean AddHoyuBox2DB(String BoxName, byte[] BoxFile,byte[] WDHPic,int BoxFileBinLen, int WDHPicBinLen, String ParameterText, byte[] XXPic,int PicBinLen, byte[] SeriousPics,int SeriousPicsBinLen,String FileLenStr) ?
回复 更多评论

re: Jni中C++和Java的参数传递 2007-10-25 15:27 vampire

c的结构提里写有一个**p,指针的指针,在java中该如何封装??? 回复 更多评论

re: Jni中C++和Java的参数传递 2007-12-11 13:13 Focus

@single
for(int i=0; i < len; i++ )
{
jobject objTemp = (env)->AllocObject(objectClass); //释放问题??这个是否需要释放不是很懂
//objectClass是函数上面给的 那个
jstring jstr = WindowsTojstring(env,”我的磁盘名字是D:”);
(env)->SetObjectField(objTemp,str,jstr);
(env)->SetShortField(objTemp,ival,i);
(env)->SetObjectArrayElement(args, i, objTemp);
}
这个 可以实现 数组 元素相同的问题

Chap8:如何将java传递过来的jbyteArray转换成C/C++中的BYTE数组

近遇到一个问题,请各位帮忙解决下:
如何将java传递过来的jbyteArray转换成C/C++中的BYTE数组?BYTE为unsigned char类型
这两个我理解应该是相同的吧,强制类型转换好像不启作用,应该如何转换呢?

该问题已经关闭: 问题已解决,之前代码有问题 jbyte * arrayBody = env->GetByteArrayElements(data,0); jsize theArrayLengthJ = env->GetArrayLength(data); BYTE * starter = (BYTE *)arrayBody;

Chap5:使用JNI技术实现java程序调用第三方dll(c/c++)文件的功能

JAVA的跨平台的特性深受java程 序员们的喜爱,但正是由于它为了实现跨平台的目的,使得它和本地机器的各种内部联系变得很少,大大约束了它的功能,比如与一些硬件设备通信,往往要花费很 大的精力去设计流程编写代码去管理设备端口,而且有一些设备厂商提供的硬件接口已经经过一定的封装和处理,不能直接使用java程序通过端口和设备通信,这种情况下就得考虑使用java程序去调用比较擅长同系统打交道的第三方程序,从1.1版本开始的JDK提供了解决这个问题的技术标准:JNI技术.
JNI是Java Native Interface(Java本地接口)的缩写,本地是相对于java程序来说的,指直接运行在操作系统之上,与操作系统直接交互的程序.从1.1版本的JDK开始,JNI就作为标准平台的一部分发行.在JNI出现的初期是为了Java程序与本地已编译语言,尤其是C和C++的互操作而设计的,后来经过扩展也可以与c和c++之外的语言编写的程序交互,例如Delphi程序.
使用JNI技术固然增强了java程序的性能和功能,但是它也破坏了java的跨平台的优点,影响程序的可移植性和安全性,例如由于其他语言(如C/C++)可能能够随意地分配对象/占用内存,Java的指针安全性得不到保证.但在有些情况下,使用JNI是可以接受的,甚至是必须的,例如上面提到的使用java程序调用硬件厂商提供的类库同设备通信等,目前市场上的许多读卡器设备就是这种情况.在这必须使用JNI的情况下,尽量把所有本地方法都封装在单个类中,这个类调用单个的本地库文件,并保证对于每种目标操作系统,都可以用特定于适当平台的版本替换这个文件,这样使用JNI得到的要比失去的多很多.
现在开始讨论上面提到的问题,一般设备商会提供两种类型的类库文件,windows系统的会包含.dll/.h/.lib文件,而linux系统的会包含.so/.a文件,这里只讨论windows系统下的c/c++编译的dll文件调用方法.
我把设备商提供的dll文件称之为第三方dll文件,之所以说第三方,是因为JNI直 接调用的是按它的标准使用c/c++语言编译的dll文件,这个文件是客户程序员按照设备商提供的.h文件中的列出的方法编写的dll文件,我称之为第二 方dll文件,真正调用设备商提供的dll文件的其实就是这个第二方dll文件.到这里,解决问题的思路已经产生了,大慨分可以分为三步:
1>编写一个java类,这个类包含的方法是按照设备商提供的.h文件经过变形/转换处理过的,并且必须使用native定义.这个地方需要注意的问题是java程序中定义的方法不必追求和厂商提供的头文件列出的方法清单中的方法具有相同的名字/返回值/参数,因为一些参数类型如指针等在java中没法模拟,只要能保证这个方法能实现原dll文件中的方法提供的功能就行了;
2>按JNI的规则使用c/c++语言编写一个dll程序;
3>按dll调用dll的规则在自己编写的dll程序里面调用厂商提供的dll程序中定义的方法.
我之前为了给一个java项目添加IC卡读写功能,曾经查了很多资料发现查到的资料都是只说到第二步,所以剩下的就只好自己动手研究了.下面结合具体的代码来按这三个步骤分析.
1>假设厂商提供的.h文件中定义了一个我们需要的方法:
__int16 __stdcall readData( HANDLE icdev, __int16 offset, __int16 len, unsigned char *data_buffer );
a.__int16定义了一个不依赖于具体的硬件和软件环境,在任何环境下都占16 bit的整型数据(java中的int类型是32 bit),这个数据类型是vc++中特定的数据类型,所以我自己做的dll也是用的vc++来编译.
b.__stdcall表示这个函数可以被其它程序调用,vc++编译的DLL欲被其他语言编写的程序调用,应将函数的调用方式声明为__stdcall方式,WINAPI都采用这种方式.c/c++语言默认的调用方式是__cdecl,所以在自己做可被java程序调用的dll时一定要加上__stdcall的声明,否则在java程序执行时会报类型不匹配的错误.
c.HANDLE icdev是windows操作系统中的一个概念,属于win32的一种数据类型,代表一个核心对象在某一个进程中的唯一索引,不是指针,在知道这个索引代表的对象类型时可以强制转换成此类型的数据.
这些知识都属于win32编程的范围,更为详细的win32资料可以查阅相关的文档.
这个方法的原始含义是通过设备初始时产生的设备标志号icdev,读取从某字符串在内存空间中的相对超始位置offset开始的共len个字符,并存放到data_buffer指向的无符号字符类型的内存空间中,并返回一个16 bit的整型值来标志这次的读设备是否成功,这里真正需要的是unsigned char *这个指针指向的地址存放的数据,而java中没有指针类型,所以可以考虑定义一个返回字符串类型的java方法,原方法中返回的整型值也可以按经过一定的规则处理按字符串类型传出,由于HANDLE是一个类型于java中的Ojbect类型的数据,可以把它当作int类型处理,这样java程序中的方法定义就已经形成了:
String readData( int icdev, int offset, int len );
声明这个方法的时候要加上native关键字,表明这是一个与本地方法通信的java方法,同时为了安全起见,此文方法要对其它类隐藏,使用private声明,再另外写一个public方法去调用它,同时要在这个类中把本地文件加载进来,最终的代码如下:
package test;
public class LinkDll
{
//从指定地址读数据
private native String readData( int icdev, int offset, int len );
public String readData( int icdev, int offset, int len )
{
return this.readDataTemp( icdev, offset, len );
}
static
{
System.loadLibrary( “TestDll” );//如果执行环境是linux这里加载的是SO文件,如果是windows环境这里加载的是dll文件
}
}
2>使用JDK的javah命令为这个类生成一个包含类中的方法定义的.h文件,可进入到class文件包的根目录下(只要是在 classpath参数中的路径即可),使用javah命令的时候要加上包名javah test.LinkDll,命令成功后生成一个名为test_LinkDll.h的头文件.
文件内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated*/

include

ifndef _Included_test_LinkDll #define

Included_test_LinkDll

ifdef __cplusplus extern “C” { #endif

/*
* Class: test_LinkDll
* Method: readDataTemp
* Signature: (III)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_test_LinkDll_readDataTemp(JNIEnv *, jobject, jint, jint, jint);

ifdef __cplusplus } #endif

endif

可以看出,JNI为了实现和dll文件的通信,已经按它的标准对方法名/参数类型/参数数目作了一定的处理,其中的JNIEnv*/jobjtct这两个参数是每个JNI方法固有的参数,javah命令负责按JNI标准为每个java方法加上这两个参数.JNIEnv是指向类型为JNIEnv_的一个特殊JNI数据结构的指针,当由C++编译器编译时JNIEnv_结构其实被定义为一个类,这个类中定义了很多内嵌函数,通过使用"->"符号,可以很方便使用这些函数,如:
(env)->NewString( jchar* c, jint len )
可以从指针c指向的地址开始读取len个字符封装成一个JString类型的数据.
其中的jchar对应于c/c++中的char,jint对应于c/c++中的len,JString对应于java中的String,通过查看jni.h可以看到这些数据类型其实都是根据java和c/c++中的数据类型对应关系使用typedef关键字重新定义的基本数据类型或结构体.
具体的对应关系如下:

Java类型 本地类型 描述
boolean jboolean C/C++8位整型
byte jbyte C/C++带符号的8位整型
char jchar C/C++无符号的16位整型
short jshort C/C++带符号的16位整型
int jint C/C++带符号的32位整型
long jlong C/C++带符号的64位整型e
float jfloat C/C++32位浮点型
double jdouble C/C++64位浮点型
Object jobject 任何Java对象,或者没有对应java类型的对象
Class jclass Class对象
String jstring 字符串对象
Object[] jobjectArray 任何对象的数组
boolean[] jbooleanArray 布尔型数组
byte[] jbyteArray 比特型数组
char[] jcharArray 字符型数组
short[] jshortArray 短整型数组
int[] jintArray 整型数组
long[] jlongArray 长整型数组
float[] jfloatArray 浮点型数组
double[] jdoubleArray 双浮点型数组
更为详细的资料可以查阅JNI文档.
需要注意的问题:test_LinkDll.h文件包含了jni.h文件;
3>使用vc++ 6.0编写TestDll.dll文件,这个文件名是和java类中loadLibrary的名称一致.
a>使用vc++6.0 新建一个Win32 Dynamic-Link Library的工程文件,工程名指定为TestDll
b>把源代码文件和头文件使用”Add Fiels to Project”菜单加载到工程中,若使用c来编码,源码文件后缀名为.c,若使用c++来编码,源码文件扩展名为.cpp,这个一定要搞清楚,因为对于不同的语言,使用JNIEnv指针的方式是不同的.
c>在这个文件里调用设备商提供的dll文件,设备商一般提供三种文件:dll/lib/h,这里假设分别为A.dll/A.lib/A.h.
这个地方的调用分为动态调用和静态调用静态调用即是只要把被调用的dll文件放到path路径下,然后加载lib链接文件和.h头文件即可直接调用A.dll中的方法:
把设备商提供的A.h文件使用”Add Fiels to Project”菜单加载到这个工程中,同时在源代码文件中要把这个A.h文件使用include包含进来;
然后依次点击”Project->settings”菜单,打开link选项卡,把A.lib添加到”Object/library modules”选项中.
具体的代码如下:
//读出数据,需要注意的是如果是c程序在调用JNI函数时必须在JNIEnv的变量名前加*,如(*env)->xxx,如果是c++程序,则直接使用(env)->xxx

include

include

include

include

include “test_LinkDll.h”

include “A.h”

JNIEXPORT jstring JNICALL Java_test_LinkDll_readDataTemp( JNIEnv *env, jobject jo, jint ji_icdev, jint ji_len )
{
//***************基本数据声明与定义********************
HANDLE H_icdev = (HANDLE)ji_icdev;//设备标志符
__int16 i16_len = (__int16)ji_len;//读出的数据长度,值为3,即3个HEX形式的字符
__int16 i16_result;//函数返回值
__int16 i16_coverResult;//字符转换函数的返回值
int i_temp;//用于循环的中间变量
jchar jca_result[3] = { ‘e’, ‘r’, ‘r’ };//当读数据错误时返回此字符串
//无符号字符指针,指向的内存空间用于存放读出的HEX形式的数据字符串
unsigned char* uncp_hex_passward = (unsigned char*)malloc( i16_len );

//无符号字符指针,指向的内存空间存放从HEX形式转换为ASC形式的数据字符串
unsigned char* uncp_asc_passward = (unsigned char*)malloc( i16_len * 2 );

//java char指针,指向的内存空间存放从存放ASC形式数据字符串空间读出的数据字符串
jchar *jcp_data = (jchar*)malloc(i16_len*2+1); 

//java String,存放从java char数组生成的String字符串,并返回给调用者
jstring js_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程序中即是String
    return (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 );
}
//**********ASC char形式的数据字符串转换为jchar形式的数据字符串***********
for ( i_temp = 0; i_temp < i16_len; i_temp++ )  
    jcp_data[i_temp] = uncp_hex_data[i_temp];

//******************jchar形式的数据字符串转换为java String****************
js_data = (env)->NewString(jcp_data,i16_len);  

return js_data;

}
动态调用,不需要lib文件,直接加载A.dll文件,并把其中的文件再次声明,代码如下:

include

include

include “test_LinkDll.h”

//首先声明一个临时方法,这个方法名可以随意定义,但参数同设备商提供的原型方法的参数保持一致.
typedef int ( readDataTemp )( int, int, int, unsigned char );//从指定地址读数据
//从指定地址读数据
JNIEXPORT jstring JNICALL Java_readDataTemp( JNIEnv *env, jobject jo, jint ji_icdev, jint ji_offset, jint ji_len )
{
int i_temp;
int i_result;
int i_icdev = (int)ji_icdev;
int i_offset = (int)ji_offset;
int i_len = (int)ji_len;
jchar jca_result[5] = { ‘e’, ‘r’, ‘r’ };
unsigned char uncp_data = (unsigned char)malloc(i_len);
jchar jcp_data = (jchar )malloc(i_len);
jstring js_data = 0;

//HINSTANCE是win32中同HANDLE类似的一种数据类型,意为Handle to an instance,常用来标记App实例,在这个地方首先把A.dll加载到内存空间,以一个App的形式存放,然后取

得它的instance交给dllhandle,以备其它资源使用.
HINSTANCE dllhandle;
dllhandle = LoadLibrary( “A.dll” );

//这个地方首先定义一个已声明过的临时方法,此临时方法相当于一个结构体,它和设备商提供的原型方法具有相同的参数结构,可互相转换
readDataTemp readData;
//使用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 < i_len; i_temp++ )
{
    jcp_data[i_temp] = uncp_data[i_temp];
}
js_data = (env)->NewString( jcp_data, i_len );
return js_data; 

}
4>以上即是一个java程序调用第三方dll文件的完整过程,当然,在整个过程的工作全部完成以后,就可以使用java类LinkDll中的public String radData( int, int, int )方法了,效果同直接使用c/c++调用这个设备商提供的A.dll文件中的readData方法几乎一样.
总结:JNI技术确实是提高了java程序的执行效率,并且扩展了java程序的功能,但它也确确实实破坏了java程序的最重要的优点:平台无关性,所以除非必须(不得不)使用JNI技术,一般还是提倡写100%纯java的程序.根据自己的经验及查阅的一些资料,把可以使用JNI技术的情况罗列如下:
1>需要直接操作物理设备,而没有相关的驱动程序,这时候我们可能需要用C甚至汇编语言来编写该设备的驱动,然后通过JNI调用;
2>涉及大量数学运算的部分,用java会带来些效率上的损失;
3>用java会产生系统难以支付的开销,如需要大量网络链接的场合;
4>存在大量可重用的c/c++代码,通过JNI可以减少开发工作量,避免重复开发.
另外,在利用JNI技术的时候要注意以下几点:
1>由于Java安全机制的限制,不要试图通过Jar文件的方式发布包含本地化方法的Applet到客户端;
2>注意内存管理问题,虽然在本地方法返回Java后将自动释放局部引用,但过多的局部引用将使虚拟机在执行本地方法时耗尽内存;
3>JNI技术不仅可以让java程序调用c/c++代码,也可以让c/c++代码调用java代码.
注:有一个名叫Jawin开源项目实现了直接读取第三方dll文件,不用自己辛苦去手写一个起传值转换作用的dll文件,有兴趣的可以研究一下.但 是我用的时候不太顺手,有很多规则限制,像自己写程序时可以随意定义返回值,随意转换类型,用这个包的话这些都是不可能的了,所以我的项目还没开始就把它 抛弃了.
Chap9:如何编写jni方法(转载)

作者:crazycow 发布时间:2008-11-16 14:44:21.0
http://blog.chinaunix.net/u1/38994/showart_1099528.html
一、概述:
在 这篇文章中将会简单介绍如何编制一些简单的JNI 方法。我们都知道JNI方法可以帮助我们调用用C/c++编写的函数,这样如果一项工作已经用C /c++语言实现的话,我们就可以不用花很大的力气再用JAVA语言对这一工作进行再实现,只要编制相应的JNI函数,就可以轻松实现JAVA语言对C /c++函数的调用,从而大大减轻程序开发人员的工作量。

在这个项目中,我们编制了很多小实例,通过阅读,运行这些小实例,你可以轻松的学会如何编制JNI方法。这篇文档可以帮助你更好的理解及实现这些实例。

现 在让我们进入主题。首先,我们看一下这个项目的体系构架。该项目分为两部分,一部分用c语言是c语言的例子,另一部分是c++语言的例子。每部分都包含 java,src源文件目录,以及一个Makefile文件。java目录中是需要调用JNI函数的JAVA源程序,含有后缀名.java。src 目录 中含有JNI函数的实现代码,包括.c或.cpp文件和.h文件。Makefile文件是对 java 、src 目录下的文件进行编译组织进而生成可执 行文件的文件。当Makefile文件执行以后还会生成以下子目录:lib , class ,bin目录 。lib 目录中包含项目中生成的静态函数库 文件libJNIExamples.so,java程序所调用的JNI方法都是通过这个库来调用的。class 目录中包含由java目录下 的.java 文件生成的.class文件。bin目录中是一个可执行的shell脚本文件。在执行该脚本的时候,项目所有程序实例的运行结果都将一并显 示在屏幕上。

具体执行步骤为:

make

cd bin

./run.sh

下面来介绍一下在这个项目中所实现的实例:

  1. 如何调用标准C/c++中的函数--例如:printf(…)
  2. 如何调用C/c++中自定义的函数
  3. 如何在jni函数中访问java类中的对象实例域
  4. 如何在jni函数中访问java类中的静态实例域
  5. 如何在jni函数中调用java对象的方法
  6. 如何在jni函数中调用java类的静态方法
  7. 如何在jni函数中传递基本数据类型参数
  8. 如何在jni函数中传递对象类型参数
  9. 如何在jni函数中处理字符串
    1. 如何在jni函数中处理数组
    2. 处理jni函数中的返回值情况
    3. 在jni中实现创建java类对象

二、基本步骤:

在介绍这些例子之前,让我们先来看看编写jni方法所需要的基本步骤,这些实例都是用c来实例来讲解,至于c++的实例和c的实例区别不大,只要作稍微的修改即可,在文档的末尾我们将介绍这些内容:

1、要想定义jni方法,首先得要在java语言中对这一方法进行声明(自然这一声明过程要在类中进行)

声明格式如下:
publicnativevoid print(); System.loadLibrary(“JNIExamples”); }

jni 函数用关键字native方法声明。

2、对该类的源文件进行编译使用javac命令,生成相应的.class文件。
3、用javah -jni为函数生成一个在java调用和实际的c函数之间的转换存根,该存根通过从虚拟机栈中取出参数信息,并将其传递给已编译的C函数来实现转换。
4、建立一个特殊的共享库,并从该共享库到处这个存根,在上面的例子中使用了System.loadLibrary,来加载libJNIExamples共享库。

三、配置运行环境:

在 编写一个简单的jni函数之前我们必须配置相应的运行环境。jdk的配置在这里就不作介绍,这里主要说的是库的路径。当调用 System.loadLibrary(..)时,编译器会到我们系统设置的库路径中寻找该库。修改路径的方法和修改任何环境变量的方法基本相同,只要在 /etc/bash.bashrc目录下增加一行LD_LIBRARY_PATH=.:./lib: (LDLIBRARYPATH)exportLDLIBRARYPATH=.:./lib: (LD_LIBRARY_PATH)

四、运行实例分析:
1、实例一:在jni中调用标准c中自带的函数printf():

下面以实例1为例来详细说明编写jni方法的详细过程。

(1)、定义包含jni函数的类Print.java:
{
/************************************************************* * the print() function will call the printf() funcion which is a ANSI c funciton * ***************************************************************/
Public native void print();
System.loadLibrary(“JNIExamples”);
}
}

在上面的实例 中,使用public native void print();语句来定义了一个Print类的jni方法。并用 Sysgem.loadLibrary(“JNIExamples”)语句来加载libJNIExamples.so库。注意:加载的语句一定要用 static关键字声明在静态块中,以保证引用该类时该库始终被加载。

(2)、对该类进行编译:javac Print.java。生成Print.class类,然后用javah 产生一个Print.h的头文件:javah Print。长生的Print.h文件格式如下:
/* DO NOT EDIT THIS FILE - it is machine generated // Header for class Print / JNIEXPORT void JNICALL Java_Print_print (JNIEnv , jobject); }

其中的加粗字体为要实现的JNI函数生命部分。

(3)、编写JNI函数的实现部分Print.c
JNIEXPORT void JNICALL Java_Print_print (JNIEnv *env, jobject obj)
{
printf(“example1:in this example a printf() function in ANSI C is called\n”);
printf(“Hello,the output is generated by printf() function in ANSI C\n”);
}

在这个文件中实现了一个简单的Jni方法。该方法调用ANSI C 中的printf()函数,输出了两个句子。

(4)、将本地函数编译到libJNIExamples.so的库中:
使用语句:gcc -fPIC -I/usr/jdk1.5/include -I/usr/jdk1.5/include/linux -shared -o libJNIExamples.so Print.c。

(5)、至此Jni函数已全部实现,可以在java代码中调用拉。
在此我们使用一个简单的类来对实现的jni方法进行测试,下面是PrintTest.java的源代码部分:
publicstaticvoid main(String[] args) { Print p = new Print(); p.print(); } }

(6)、对PrintTest.java进行编译执行得到如下结果:
example1:in this example a printf() function in ANSI C is called
Hello,the output is generated by printf() function in ANSI C .

下面介绍的每个实例实现的步骤也都是按着上述步骤执行的。所以介绍时只介绍实现的关键部分。

2、实例二、调用c 语言用户定义的函数
(源程序为:java/Cfunction.java java/C_functionTest.java src/Cfunction.c src/Cfunction.h )
当需要在java程序中调用用c所实现的函数是,需要在需要调用该c函数的类中定义一个jni方法,在该jni方法中去调用该c函数,相当于用java方法把c函数封装起来,以供java程序调用。
在实例二中我们简单定义了一个printHello()函数,该函数的功能只是输出一句话,如果要在java程序中调用该函数,只需在jni函数中调用即可,和调用ANSI C中自带的prinf()函数没有任何区别。

3、实例三、在jni函数中访问java类中的对象实例域
(源程序为:java/CommonField.java java/CommonFieldTest.java src/CommonField.c src/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类,其中包含int a , int b 两 个实例域,我们要在jni函数getCommonField()中对这两个域进行访问和修改。你可以在 src/CommonField.c中找到该函数 的实现部分。
以下语句是对该域的访问(以下代码摘自:src/CommonField.c):
jclass class_Field = (*env)->GetObjectClass(env,obj);
jfieldID fdA = (*env)->GetFieldID(env,class_Field,”a”,”I”);
jfieldID fdB = (*env)->GetFieldID(env,class_Field,”b”,”I”);
jint valueA = (*env)->GetIntField(env,obj,fdA);
jint valueB = (*env)->GetIntField(env,obj,fdB);

在jni 中对所有jni函数的调用都要用到env指针,该指针也是每一个本地方法的第一个参数,他是函数指针表的指针,所以,必须在每一个jni调用前面加上 (*env)->GetObjectClass(env,obj)函数调用返回obj对像的类型,其中obj 参数表示要你想要得到类型的类对象。
jfieldID GetFieldID(JNIEnv *env,jclass cl, const char name[], const char sig[]) 该 函数返回一个域的标识符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);

这和获得域的值类似,只是该函数多了一个要设置给该域的值参数。
访问对象实例域的相关函数如下:
jfieldID GetFieldID(JNIEnv *env, jclass cl, const char name[], const char sig[])
该函数返回一个域的标识符。各参数含义如下:
env JNI 接口指针;cl 类对象 ; name 域名; sig 编码的域签名

XXX GetXXXField(JNIEnv *env, jobject obj, jfieldID id)
该函数返回域的值。域类型XXX是Object, Boolean, byte, char , short, int ,long ,float, double 中类型之一。
参数 env JNI借口指针;obj为域所在对象;id为域的标识符。
void SetXXXField(JNIEnv *env,jobject obj, jfieldID id, XXX value)

该函数用于设置域的值。XXX的含义同上,
参数中env, obj , id 的含义也同上,value 值为将要设置的值。

4、实例四:在jni函数中访问类的静态实例域
(java/Field.java java/FieldTest.java src/Field.c src/Field.h)
因为静态实例域并不属于某个对象,而是属于一个类,所以在要访问静态实例域时,和访问对象的实例域不同,它所调用的函数是(以实例四来说明,一下代码摘自src/Field.c):
jclass class_Field = (*env)->FindClass(env,”Field”);
jfieldID fdA = (*env)->GetStaticFieldID(env,class_Field,”a”,”I”);
jint valueA = (*env)->GetStaticIntField(env,class_Field,fdA);
(*env)->SetStaticIntField(env,class_Field,fdA,111);
由于没有 对象,必须使用FindClass代替GetObjectClass来获得类引用。在FindClass()的第二个参数是类的编码签名,类的编码签名和 基本类型的编码签名有所不同,如果类在当前包中,就直接是类的名称,如果类不在当前包中则要加入该类的详细路径:例如String类在java.lang 包中,则String的签名要写成( Ljava/lang/String;),其中的(L和;)是不可少的,其中(;)是表达是的终止符。其他三个函数 和访问对象数据域基本没什么区别。

5、实例五:在jni函数中调用java对象的方法
(java/CommonMethod.java java/CommonMethodTest.java src/CommonMehod.c src/CommonMethod.h )

在jni函数中我们不仅要对java对象的数据域进行访问,而且有时也需要调用java中类对象已经实现的方法,实例五就是关于这方面的实现的。在src/CommonMethod.c中我们可以找到下面的代码:
JNIEXPORT void JNICALL Java_CommonMethod_callMethod (
JNIEnv *env, jobject obj, jint a, jstring s)
{
printf(“example 5:in this example,a object’s method will be called\n”);
jclass class_CommonMethod = (*env)->GetObjectClass(env,obj);
jmethodID md = (*env)->GetMethodID(env,class_CommonMethod,”print”,
“(ILjava/lang/String;)V”);
(*env)->CallVoidMethod(env,obj,md,a,s);
}

该代码部分展示了如何实现对java类对象函数的调用过程。从以上代码部分我们可以看到,要实现该调用需要有三个步骤,调用三个函数
jclass class_CommonMethod = (*env)->GetObjectClass(env,obj);
jmethodID md = (*env)->GetMethodID(env,class_CommonMethod,”print”,”(ILjava/lang/String;)V”);
(*env)->CallVoidMethod(env,obj,md,a,s);

GetObjectClass(…)函数获得要调用对象的类;GetMethodID(…)获得要调用的方法相对于该类的ID号;CallXXXMethod(…)调用该方法。
在 编写该调用过程的时候,需要注意的仍然是GetMethodID(…)函数中编码签名的问题,在该实例中,我们要做的是找到CommonMethod 类的print(int a, String s)方法,该方法打印整数a,和字符串s 的直。在函数的编码签名部分(该部分以加粗、并加有下划 线)GetMethodID(env,class_CommonMethod,”print”,”(ILjava/lang /String;)V”); 从左往右可以查看,括号中的内容为要调用方法的参数部分内容,I表示第一个参数为int类型,“Ljava/lang /String;”表示第二个参数为String类型,V表示返回值类型为空void,如果返回值类型不为空,则使用相应的类型签名。返回值类型是和下面 将要使用的调用该方法的函数CallXXXMethod(…)相关联的,该函数的xxx要用相应的类型来替换,在此实例中为void,如果返回值类型 为int类型则调用该方法的函数就为CallIntMethod(…)。

6、实例六:在jni函数中调用java类的静态方法
(java/Method.java java/MethodTest.java src/Method.h src/Method.c)

实例五中介绍了如何调用类对象的方法,在此实例中我们将介绍如何调用java类的静态方法在此实例中我们在/java/Method.java中定义了静态方法:

public static void print() {
System.out.println(“this is a static method of class Method”);
}

该函数的功能就是打印字符串“ this is a static method of class Method”;
我们在src/Method.c中实现了对该方法调用的jni函数:
JNIEXPORT void JNICALL Java_Method_callMethod (JNIEnv *env, jobject obj)
{
printf(“example 6:in this example, the class’s static method will be called\n”);
jclass class_Method = (*env)->FindClass(env,”Method”);
jmethodID md = (*env)->GetStaticMethodID(env,class_Method,”print”,”()V”);
(*env)->CallStaticVoidMethod(env,class_Method,md); }

和实例五不同的是,我们要调用的三个函数变为:
FindClass(…)、GetStaticMethodID(…)、CallStaticVoidMethod(…)。
其中的机制和实例五是一样的。再次就不做过多的介绍。

7、实例七:jni函数中传递基本数据类型参数
(java/Basic.java java/BasicTest.java src/Basic.c src/Basic.h) 在java/Basic.java中,我们定义了一个public native void raiseValue(int a)函数,该函数将打印使value的值增加a,并打印原来的value和新的value值。
在src/Basic.c中给出了该jni函数的实现部分。
JNIEXPORT void JNICALL Java_Basic_raiseValue (
JNIEnv *env, jobject obj, jint a)
{
printf(“example 7: in this example, a integer type parament will be passed to the jni method\n”);
jclass class_Basic = (*env)->GetObjectClass(env,obj);
jfieldID fd = (*env)->GetFieldID(env,class_Basic,”value”,”I”);
jint v = (*env)->GetIntField(env,obj,fd);
v = v+a;
(*env)->SetIntField(env,obj,fd,v);
}

在此函数实现中,因为要访问 Basic类中的value域,所以调用了 GetObjectClass(…), GetFieldID(…), GetIntField(…)函数获取value值,下面一步 的 “ = v+a; ”说明,传递基本类型参数的处理方式和在c语言中的基本数据类型的处理无异。

8、实例八:在jni函数中传递对象类型参数
(java/Book.java java/BookTest.java src/BookTest.c src/BookTest.h)

在该实例中演示了在jni函数中传递对象函数的过程。

我们在该实例中定义了一个类Book
total_page = t;
}
publicint getTotalPage() { }
publicint getCurrentPage() { }
current_page++;
}
}

然后我们在java/BookTest.java中定义jni函数
public native void bookCurrentStatus(Book b);
该函数需要一个Book类型的参数,并返回该参数的当前状态,包括该书一共有多少页的total_page,以及当前页current_page。函数的实现部分为(src/BookTest.c)

JNIEXPORT void JNICALL Java_BookTest_bookCurrentStatus (JNIEnv *env,
jobject this_obj, jobject obj)
{
printf(“example 8: in this example, a object parament will be passed to the jni method。\n”);
jclass class_book = (*env)->GetObjectClass(env,obj);
jmethodID id_getTotal = (*env)->GetMethodID(env,
class_book,”getTotalPage”,”()I”);
jmethodID id_getCurrent = (*env)->GetMethodID(env,
class_book,”getCurrentPage”,”()I”);
jint total_page = (*env)->CallIntMethod(env,
obj,id_getTotal);
jint current_page = (*env)->CallIntMethod(env,
obj,id_getCurrent);

printf(“the total page is:%d and the current page is :%d\n”,
total_page,current_page);
}

该函数包含三个参数(JNIEnv *env, jobject this_obj, jobject obj) ,第二 个jobject this_obj参数表示当前的jni 函数所属于的类对象,第三个jobject obj参数表示传递的参数Book类型的类对象。
对于实现部分,基本和实例五--调用java类对象的方法中的操作相同,就不作详解。

9、实例九:在jni函数中处理字符串
(java/Str.java java/StrTest.java src/Str.c src/Str.h)
在该实例中我们讲解如何传递、处理字符串参数。
在java/Str.java中我们定义了一个 printString(String s) 的方法,用来处理字符串参数。
在src/Str.c中我们可以看到该函数的实现部分:
JNIEXPORT void JNICALL Java_Str_printString (JNIEnv *env,
jobject obj, jstring s)
{
printf(“example 9: in this example, a String object parament will be passed to the jni method.\n”);
const char* string = (char*)(*env)->GetStringUTFChars(env,s,NULL);
printf(“%s is put out in native method\n”,string);
(env)->ReleaseStringUTFChars(env,s,(jbyte)string);
}

实现过程中调用了两个函数:GetStringUTFChars(…)、 ReleaseStringUTFChars(…)。
GetStringUTFChars(…) 用来获取String对象的字符串,并将其抓那还为char*类型,这应该字符串就可以在c语言中进行处理拉。 ReleaseStringUTFChars(…)用于当该字符串使用完成后,将其进行垃圾回收。记住,当使用完字符串时一定不要忘记调用该函数。

10、实例十:在jni函数中处理数组
(java/Arr.java java/ArrTest.java src/Arr.c src/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(int intArry[]),该函数的功能为对该数组进行输出,在src/Arr.c中我们可以看到该方法的实现过程如下:
JNIEXPORT void JNICALL Java_Arr_print (JNIEnv *env,
jobject obj, jintArray intArray)
{
printf(“example 10:in this example, a array parament will be passed to the jni method.\n”);
jint* arr = (*env)->GetIntArrayElements(env,intArray,NULL);
n = (*env)->GetArrayLength(env,intArray);
printf(“the native method output the int array\n”);
for( i = 0;i<(*env)->GetArrayLength(env,intArray);i++)
{
printf(“%d “,arr[i]);
}
(*env)->ReleaseIntArrayElements(env,intArray,arr,0);
}

我们在此调用了GetIntArrayElements(…)来获取一个指向intArray[]数组第一个元素的指针。
用getArrayLength(..)函数来得到数组的长度,以方便数组遍历时使用。最后应用ReleaseArrayElements(…)函数来释放该数组指针。

11、实例十一:在jni中的返回值问题
(java/ReturnValue.java java/ReturnValueTest.java java/BookClass.java src/ReturnValue.c src/ReturnValue.h)
在java/ReturnValue类中定义了三个jni方法: returnInt(),returnString() ,returnObject()
三个方法,分别返回int , String , Object 类型的值。
其在src/ReturnValue.c中的实现分别为:
JNIEXPORT jint JNICALL Java_ReturnValue_returnInt (
JNIEnv *env, jobject obj)
{
jclass class_ReturnValue = (*env)->GetObjectClass(env,obj);
jfieldID fd = (*env)->GetFieldID(env,class_ReturnValue,”value”,”I”);
jint v = (*env)->GetIntField(env,obj,fd);
return v;
}

  • Signature: ()Ljava/lang/String;
    JNIEXPORT jstring JNICALL Java_ReturnValue_returnString (
    JNIEnv *env, jobject obj)
    {
    printf(“example 11: in this example, the int and object of return value will be proceeding\n”);
    jclass class_ReturnValue = (*env)->GetObjectClass(env,obj);
    jfieldID fd = (*env)->GetFieldID(env,class_ReturnValue,”name”,”Ljava/lang/String;”);
    jstring jstr = (jstring)(*env)->GetObjectField(env,obj,fd);
    }

    • Method: returnObject
      JNIEXPORT jobject JNICALL Java_ReturnValue_returnObject (
      JNIEnv *env, jobject obj)
      {
      jclass class_ReturnValue = (*env)->GetObjectClass(env,obj);
      jfieldID fd = (*env)->GetFieldID(env,class_ReturnValue,”myBook”,”LBookClass;”);
      jobject jbook = (jstring)(*env)->GetObjectField(env,obj,fd);
      }

在这里分别涉及到了对java类对象的一般参数,String参数,以及Object参数的访问。

12、实例十二:在jni中创建java类对象:
(java/Test.java src/CreateObj.c src/CreateObj.h)

如果想要在jni函数创建java类对象则要引用java 类的构造器方法,通过调用NewObject函数来实现。
NewObject函数的调用方式为:
jobject obj_new = (*env)->NewObject(env,class, methodid, paraments);
在该实例中,我们在java/Test.java 中定义了Book1类,要在CreateObj类的modifyProperty() jni方法中创建该类对象。我们可以在src/CreateObj.c中看到该jni方法创建对象的过程:
jobject book;
jclass class_book;
jmethodID md_book;
class_book = (*env)->FindClass(env,”LBook1;”);
md_book = (*env)->GetMethodID(env,class_book,””,”(IILjava/lang/String;)V”);
book = (*env)->NewObject(env,class_book,md_book,100,1,”huanghe”);

在 创建对象的过程中可以看到,要创建一个java类对象,首先需要得到得到使用FindClass函数得到该类,然后使用GetMethodID方法得到该 类的构造器方法id,主义在此时构造器的函数名始终为:””,其后函数的签名要符合函数签名规则。在此我们的构造器有三个参 数:int , int, String.
并且其返回值类型要永久为空,所以函数签名为:”(IILjava/lang/String;)V”
然后我们调用NewObject()函数来创建该类的对象,在此之后就可以使用该对象拉。

以上内容介绍的是jni函数c语言的实现实例。如果想要使用c++的实例,我们只需要把其中的每一个函数调用过程作稍微的修改:
例如:(*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++实例代码.

Chap10:在 Windows 中实现 Java 本地方法

级别: 初级
David WendtWebSphere Development Research Triangle Park, NC
1999 年 5 月 01 日
本文为在 32 位 Windows 平台上实现 Java 本地方法提供了实用的示例、步骤和准则。这些示例包括传递和返回常用的数据类型。
本文中的示例使用 Sun Microsystems 公司创建的 Java DevelopmentKit (JDK) 版本 1.1.6 和 Java本地接口 (JNI) 规范。 用 C 语言编写的本地代码是用 MicrosoftVisual C++ 编译器编译生成的。
简介
本文提供调用本地 C 代码的 Java 代码示例,包括传递和返回某些常用的数据类型。本地方法包含在特定于平台的可执行文件中。就本文中的示例而言,本地方法包含在 Windows 32 位动态链接库 (DLL) 中。
不过我要提醒您,对 Java 外部的调用通常不能移植到其他平台上,在 applet 中还可能引发安全异常。实现本地代码将使您的 Java 应用程序无法通过 100% 纯 Java 测试。但是,如果必须执行本地调用,则要考虑几个准则:
1.将您的所有本地方法都封装在单个类中,这个类调用单个 DLL。对于每种目标操作系统,都可以用特定于适当平台的版本替换这个 DLL。这样就可以将本地代码的影响减至最小,并有助于将以后所需的移植问题包含在内。
2.本地方法要简单。尽量将您的 DLL 对任何第三方(包括 Microsoft)运行时 DLL 的依赖减到最小。使您的本地方法尽量独立,以将加载您的 DLL 和应用程序所需的开销减到最小。如果需要运行时 DLL,必须随应用程序一起提供它们。

,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
1.Java 调用 C
对于调用 C 函数的 Java 方法,必须在 Java 类中声明一个本地方法。在本部分的所有示例中,我们将创建一个名为 MyNative 的类,并逐步在其中加入新的功能。这强调了一种思想,即将本地方法集中在单个类中,以便将以后所需的移植工作减到最少。

示例 1 – 传递参数
在第一个示例中,我们将三个常用参数类型传递给本地函数: String、 int和 boolean 。本例说明在本地 C 代码中如何引用这些参数。
public class MyNative
{
public void showParms( String s, int i, boolean b )
{
showParms0( s, i , b );
}
private native void showParms0( String s, int i, boolean b );
static
{
System.loadLibrary( “MyNative” );
}
}

请注意,本地方法被声明为专用的,并创建了一个包装方法用于公用目的。这进一步将本地方法同代码的其余部分隔离开来,从而允许针对所需的平台对它进行优化。 static子句加载包含本地方法实现的 DLL。
下一步是生成 C 代码来实现 showParms0 方法。此方法的 C 函数原型是通过对 .class 文件使用 javah 实用程序来创建的,而 .class 文件是通过编译 MyNative.java 文件生成的。这个实用程序可在 JDK 中找到。下面是 javah 的用法:
javac MyNative.java(将 .java 编译为 .class)
javah -jni MyNative(生成 .h 文件)

这将生成一个 MyNative.h 文件,其中包含一个本地方法原型,如下所示:
/*
* Class: MyNative
* Method: showParms0
* Signature: (Ljava/lang/String;IZ)V
*/
JNIEXPORT void JNICALL Java_MyNative_showParms0
(JNIEnv *, jobject, jstring, jint, jboolean);

第一个参数是调用 JNI 方法时使用的 JNI Environment 指针。第二个参数是指向在此 Java 代码中实例化的 Java 对象 MyNative 的一个句柄。其他参数是方法本身的参数。请注意,MyNative.h 包括头文件 jni.h。jni.h 包含 JNI API 和变量类型(包括jobject、jstring、jint、jboolean,等等)的原型和其他声明。
本地方法是在文件 MyNative.c 中用 C 语言实现的:

include

include “MyNative.h”

JNIEXPORT void JNICALL Java_MyNative_showParms0
(JNIEnv *env, jobject obj, jstring s, jint i, jboolean b)
{
const char* szStr = (*env)->GetStringUTFChars( env, s, 0 );
printf( “String = [%s]\n”, szStr );
printf( “int = %d\n”, i );
printf( “boolean = %s\n”, (b==JNI_TRUE ? “true” : “false”) );
(*env)->ReleaseStringUTFChars( env, s, szStr );
}

JNI API,GetStringUTFChars,用来根据 Java 字符串或 jstring 参数创建 C 字符串。这是必需的,因为在本地代码中不能直接读取 Java 字符串,而必须将其转换为 C 字符串或 Unicode。有关转换 Java 字符串的详细信息,请参阅标题为 NLS Strings and JNI 的一篇论文。但是,jboolean 和 jint 值可以直接使用。
MyNative.dll 是通过编译 C 源文件创建的。下面的编译语句使用 Microsoft Visual C++ 编译器:
cl -Ic:\jdk1.1.6\include -Ic:\jdk1.1.6\include\win32 -LD MyNative.c
-FeMyNative.dll

其中 c:\jdk1.1.6 是 JDK 的安装路径。
MyNative.dll 已创建好,现在就可将其用于 MyNative 类了。
可以这样测试这个本地方法:在 MyNative 类中创建一个 main 方法来调用 showParms 方法,如下所示:
public static void main( String[] args )
{
MyNative obj = new MyNative();
obj.showParms( “Hello”, 23, true );
obj.showParms( “World”, 34, false );
}

当运行这个 Java 应用程序时,请确保 MyNative.dll 位于 Windows 的 PATH 环境变量所指定的路径中或当前目录下。当执行此 Java 程序时,如果未找到这个 DLL,您可能会看到以下的消息:
java MyNative
Can’t find class MyNative

这是因为 static 子句无法加载这个 DLL,所以在初始化 MyNative 类时引发异常。Java 解释器处理这个异常,并报告一个一般错误,指出找不到这个类。
如果用 -verbose 命令行选项运行解释器,您将看到它因找不到这个 DLL 而加载 UnsatisfiedLinkError 异常。
如果此 Java 程序完成运行,就会输出以下内容:
java MyNative
String = [Hello]
int = 23
boolean = true
String = [World]
int
= 34

boolean = false 示例 2 – 返回一个值
本例将说明如何在本地方法中实现返回代码。
将这个方法添加到 MyNative 类中,这个类现在变为以下形式:
public class MyNative
{
public void showParms( String s, int i, boolean b )
{
showParms0( s, i , b );
}
public int hypotenuse( int a, int b )
{
return hyptenuse0( a, b );
}
private native void showParms0( String s, int i, boolean b );
private native int hypotenuse0( int a, int b );
static
{
System.loadLibrary( “MyNative” );
}
/* 测试本地方法 */
public static void main( String[] args )
{
MyNative obj = new MyNative();
System.out.println( obj.hypotenuse(3,4) );
System.out.println( obj.hypotenuse(9,12) );
}
}

公用的 hypotenuse 方法调用本地方法 hypotenuse0 来根据传递的参数计算值,并将结果作为一个整数返回。这个新本地方法的原型是使用 javah 生成的。请注意,每次运行这个实用程序时,它将自动覆盖当前目录中的 MyNative.h。按以下方式执行 javah:
javah -jni MyNative

生成的 MyNative.h 现在包含 hypotenuse0 原型,如下所示:
/*
* Class: MyNative
* Method: hypotenuse0
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0
(JNIEnv *, jobject, jint, jint);

该方法是在 MyNative.c 源文件中实现的,如下所示:

include

include

include “MyNative.h”

JNIEXPORT void JNICALL Java_MyNative_showParms0
(JNIEnv *env, jobject obj, jstring s, jint i, jboolean b)
{
const char* szStr = (*env)->GetStringUTFChars( env, s, 0 );
printf( “String = [%s]\n”, szStr );
printf( “int = %d\n”, i );
printf( “boolean = %s\n”, (b==JNI_TRUE ? “true” : “false”) );
(*env)->ReleaseStringUTFChars( env, s, szStr );
}
JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0
(JNIEnv *env, jobject obj, jint a, jint b)
{
int rtn = (int)sqrt( (double)( (a*a) + (b*b) ) );
return (jint)rtn;
}

再次请注意,jint 和 int 值是可互换的。
使用相同的编译语句重新编译这个 DLL:
cl -Ic:\jdk1.1.6\include -Ic:\jdk1.1.6\include\win32 -LD MyNative.c
-FeMyNative.dll

现在执行 java MyNative 将输出 5 和 15 作为斜边的值。
示例 3 – 静态方法
您可能在上面的示例中已经注意到,实例化的 MyNative 对象是没必要的。实用方法通常不需要实际的对象,通常都将它们创建为静态方法。本例说明如何用一个静态方法实现上面的示例。更改 MyNative.java 中的方法签名,以使它们成为静态方法:
public static int hypotenuse( int a, int b )
{
return hypotenuse0(a,b);
}

private static native int hypotenuse0( int a, int b );

现在运行 javah 为 hypotenuse0创建一个新原型,生成的原型如下所示:
/*
* Class: MyNative
* Method: hypotenuse0
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0
(JNIEnv *, jclass, jint, jint);

C 源代码中的方法签名变了,但代码还保持原样:
JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0
(JNIEnv *env, jclass cls, jint a, jint b)
{
int rtn = (int)sqrt( (double)( (a*a) + (b*b) ) );
return (jint)rtn;
}

本质上,jobject 参数已变为 jclass 参数。此参数是指向 MyNative.class 的一个句柄。main 方法可更改为以下形式:
public static void main( String[] args )
{
System.out.println( MyNative.hypotenuse( 3, 4 ) );
System.out.println( MyNative.hypotenuse( 9, 12 ) );
}

因为方法是静态的,所以调用它不需要实例化 MyNative 对象。本文后面的示例将使用静态方法。
示例 4 – 传递数组
本例说明如何传递数组型参数。本例使用一个基本类型,boolean,并将更改数组元素。下一个示例将访问 String(非基本类型)数组。将下面的方法添加到 MyNative.java 源代码中:
public static void setArray( boolean[] ba )
{
for( int i=0; i < ba.length; i++ )
ba[i] = true;
setArray0( ba );
}

private static native void setArray0( boolean[] ba );

在本例中,布尔型数组被初始化为 true,本地方法将把特定的元素设置为 false。同时,在 Java 源代码中,我们可以更改 main 以使其包含测试代码:
boolean[] ba = new boolean[5];
MyNative.setArray( ba );
for( int i=0; i < ba.length; i++ )
System.out.println( ba[i] );

在编译源代码并执行 javah 以后,MyNative.h 头文件包含以下的原型:
/*
* Class: MyNative
* Method: setArray0
* Signature: ([Z)V
*/
JNIEXPORT void JNICALL Java_MyNative_setArray0
(JNIEnv *, jclass, jbooleanArray);

请注意,布尔型数组是作为单个名为 jbooleanArray 的类型创建的。
基本类型有它们自已的数组类型,如 jintArray 和 jcharArray。
非基本类型的数组使用 jobjectArray 类型。下一个示例中包括一个 jobjectArray。这个布尔数组的数组元素是通过 JNI 方法 GetBooleanArrayElements 来访问的。
针对每种基本类型都有等价的方法。这个本地方法是如下实现的:
JNIEXPORT void JNICALL Java_MyNative_setArray0
(JNIEnv *env, jclass cls, jbooleanArray ba)
{
jboolean* pba = (*env)->GetBooleanArrayElements( env, ba, 0 );
jsize len = (*env)->GetArrayLength(env, ba);
int i=0;
// 更改偶数数组元素
for( i=0; i < len; i+=2 )
pba[i] = JNI_FALSE;
(*env)->ReleaseBooleanArrayElements( env, ba, pba, 0 );
}

指向布尔型数组的指针可以使用 GetBooleanArrayElements 获得。
数组大小可以用 GetArrayLength 方法获得。使用 ReleaseBooleanArrayElements 方法释放数组。现在就可以读取和修改数组元素的值了。jsize 声明等价于 jint(要查看它的定义,请参阅 JDK 的 include 目录下的 jni.h 头文件)。
示例 5 – 传递 Java String 数组
本例将通过最常用的非基本类型,Java String,说明如何访问非基本对象的数组。字符串数组被传递给本地方法,而本地方法只是将它们显示到控制台上。
MyNative 类定义中添加了以下几个方法:
public static void showStrings( String[] sa )
{
showStrings0( sa );
}
private static void showStrings0( String[] sa );

并在 main 方法中添加了两行进行测试:
String[] sa = new String[] { “Hello,”, “world!”, “JNI”, “is”, “fun.” };
MyNative.showStrings( sa );

本地方法分别访问每个元素,其实现如下所示。
JNIEXPORT void JNICALL Java_MyNative_showStrings0
(JNIEnv *env, jclass cls, jobjectArray sa)
{
int len = (*env)->GetArrayLength( env, sa );
int i=0;
for( i=0; i < len; i++ )
{
jobject obj = (*env)->GetObjectArrayElement(env, sa, i);
jstring str = (jstring)obj;
const char* szStr = (*env)->GetStringUTFChars( env, str, 0 );
printf( “%s “, szStr );
(*env)->ReleaseStringUTFChars( env, str, szStr );
}
printf( “\n” );
}

数组元素可以通过 GetObjectArrayElement 访问。
在本例中,我们知道返回值是 jstring 类型,所以可以安全地将它从 jobject 类型转换为 jstring 类型。字符串是通过前面讨论过的方法打印的。有关在 Windows 中处理 Java 字符串的信息,请参阅标题为 NLS Strings and JNI 的一篇论文。
示例 6 – 返回 Java String 数组
最后一个示例说明如何在本地代码中创建一个字符串数组并将它返回给 Java 调用者。MyNative.java 中添加了以下几个方法:
public static String[] getStrings()
{
return getStrings0();
}
private static native String[] getStrings0();

更改 main 以使 showStrings 将 getStrings 的输出显示出来:
MyNative.showStrings( MyNative.getStrings() );

实现的本地方法返回五个字符串。
JNIEXPORT jobjectArray JNICALL Java_MyNative_getStrings0
(JNIEnv *env, jclass cls)
{
jstring str;
jobjectArray args = 0;
jsize len = 5;
char* sa[] = { “Hello,”, “world!”, “JNI”, “is”, “fun” };
int i=0;
args = (*env)->NewObjectArray(env, len, (*env)->FindClass(env, “java/lang/String”), 0);
for( i=0; i < len; i++ )
{
str = (*env)->NewStringUTF( env, sa[i] );
(*env)->SetObjectArrayElement(env, args, i, str);
}
return args;
}

字符串数组是通过调用 NewObjectArray 创建的,同时传递了 String 类和数组长度两个参数。Java String 是使用 NewStringUTF 创建的。String 元素是使用 SetObjectArrayElement 存入数组中的。

2.调试
现 在您已经为您的应用程序创建了一个本地 DLL,但在调试时还要牢记以下几点。如果使用 Java 调试器 java_g.exe,则还需要创建 DLL 的一个“调试”版本。这只是表示必须创建同名但带有一个 _g 后缀的 DLL 版本。就 MyNative.dll 而言,使用 java_g.exe 要求在 Windows 的 PATH 环境指定的路径中有一个 MyNative_g.dll 文件。在大多数情况下,这个 DLL 可以通过将原文件重命名或复制为其名称带缀 _g 的文件。
现在,Java 调试器不允许您进入本地代码,但您可以在 Java 环境外使用 C 调试器(如 Microsoft Visual C++)调试本地方法。首先将源文件导入一个项目中。
将编译设置调整为在编译时将 include 目录包括在内:
c:\jdk1.1.6\include;c:\jdk1.1.6\include\win32

将配置设置为以调试模式编译 DLL。在 Project Settings 中的 Debug 下,将可执行文件设置为 java.exe(或者 java_g.exe,但要确保您生成了一个 _g.dll 文件)。程序参数包括包含 main 的类名。如果在 DLL 中设置了断点,则当调用本地方法时,执行将在适当的地方停止。
下面是设置一个 Visual C++ 6.0 项目来调试本地方法的步骤。
1.在 Visual C++ 中创建一个 Win32 DLL 项目,并将 .c 和 .h 文件添加到这个项目中。

在 Tools 下拉式菜单的 Options 设置下设置 JDK 的 include 目录。下面的对话框显示了这些目录。

选择 Build 下拉式菜单下的 Build MyNative.dll 来建立这个项目。确保将项目的活动配置设置为调试(这通常是缺省值)。
在 Project Settings 下,设置 Debug 选项卡来调用适当的 Java 解释器,如下所示:

当执行这个程序时,忽略“在 java.exe 中找不到任何调试信息”的消息。当调用本地方法时,在 C 代码中设置的任何断点将在适当的地方停止 Java 程序的执行。

3.其他信息
JNI 方法和 C++
上面这些示例说明了如何在 C 源文件中使用 JNI 方法。如果使用 C++,则请将相应方法的格式从:
(*env)->JNIMethod( env, …. );

更改为:
env->JNIMethod( … );

在 C++ 中,JNI 函数被看作是 JNIEnv 类的成员方法。
字符串和国家语言支持
本文中使用的技术用 UTF 方法来转换字符串。使用这些方法只是为了方便起见,如果应用程序需要国家语言支持 (NLS),则不能使用这些方法。有关在 Windows 和 NLS 环境中处理 Java 字符串正确方法,请参标题为 NLS Strings and JNI 的一篇论文。
4.小结
本文提供的示例用最常用的数据类据(如 jint 和 jstring)说明了如何实现本地方法,并讨论了 Windows 特定的几个问题,如显示字符串。本文提供的示例并未包括全部 JNI,JNI 还包括其他参数类型,如 jfloat、jdouble、jshort、jbyte 和 jfieldID,以及用来处理这些类型的方法。有关这个主题的详细信息,请参阅 Sun Microsystems 提供的 Java 本地接口规范。
5.关于作者: David Wendt 是 IBM WebSphere Studio 的一名程序员,该工作室位于北卡罗莱纳州的 Research Triangle Park。可以通过 wendt@us.ibm.com 与他联系。

Chap11:JNI编程系列之基础篇
编程技术 2008-12-06 19:50 阅读2 评论0
字号: 大 中 小
JNI编程系列之基础篇

最近干一个活需要从Java调用C++编译的动态链接库,研究了一下JNI,现在将网上搜罗的文档和自己的体会贡献出来。

JNI的做法是:通过在方法前加上关键字native来识别本地方法,然后用本地语言(如C,C++)来实现该方法,并编译成动态链接库,在Java的类中调用该动态链接库,然后就可以像使用Java自己的方法一样使用native方法了。这样做的好处是既具有了Java语言的便利性,又具有了C语言的效率;另一个好处是可以利用已有的C代码,避免重复开发。
下面从最简单的JNI程序入手,介绍如何进行JNI编程。

下面是一个简单的Java程序HelloWorld.java,

class HelloWorld {
private native void print();
public static void main(String[] args) {
new HelloWorld().print();
}
static {
System.loadLibrary(“HelloWorld”);
}
}

在这个例子中,注意到两个关键的地方。
首先是第二行
private native void print();
如果没有native关键字,这一行代码就是普通Java方法的声明。关键字native表明这是一个用本地语言实现的方法。
第二个地方是
System.loadLibrary(“HelloWorld”);
这行代码的作用是调用名为HelloWorld的动态链接库,在Windows下,是HelloWorld.dll,在Linux下是HelloWorld.so。

显然现在这个Java程序是不能运行的。要运行它先要做下面的工作。执行

javac HelloWorld.java
javah -jni HelloWorld

执行完这两条语句之后,会生成一个名为HelloWorld.h的文件,它的内容应该是这样的,

/* DO NOT EDIT THIS FILE - it is machine generated */

include

ifndef _Included_HelloWorld

define _Included_HelloWorld

ifdef __cplusplus

extern “C” {

endif

/*
* Class: HelloWorld
* Method: print
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *, jobject);

ifdef __cplusplus

}

endif

endif

注意到在这个程序的开头有这样一行代码,

include

include “HelloWorld.h”

include

JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *, jobject) {
std::cout << “Hello World!” << std::endl;
}
//————–

这是一个最简单的C++程序。将它编译为动态链接库,我们得到HelloWorld.dll,将这个.dll文件拷到HelloWorld.java文件所在的目录下。执行

java HelloWorld
你会看到屏幕上输出
Hello World!

现在来总结一下,要实现JNI编程,需要以下几个步骤:
1. 写一个Java程序,将你希望用C语言实现的方法用native关键字标识出来,同时加上调用动态链接库的语句。
System.loadLibrary(“HelloWorld”);
2. 执行下面两条语句,生成.h文件

javac HelloWorld.java

在class或bin目录下(其下或其子目录下有 javac命令生成的*.class文件)执行

javah -jni HelloWorld
3. 根据.h文件,写一个.cpp程序,编译成动态链接库,并将其复制到.java文件所在的路径下。
4. 执行java HelloWorld
这样,就学会了最简单的JNI编程,网上能google到的大部分文章也就到此为止了。但是你一定还有很多疑问,就像我刚开始一样,最容易想到的就是,如果本地方法要传递参数或者返回值怎么办?本地方法的定义在.java文件中,参数或者返回值的类型都是Java的类型。而它的实现是通过C程序完成的,参数和返回值的类型只能是C的类型。诸如此类的问题,上面这个简单的例子是回答不了的。在下一篇,我将解释这些问题。

Chap12:JNI编程系列之中级篇(上)

编程技术 2008-12-06 23:41 阅读9 评论0
字号: 大 中 小
本篇将介绍在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++类型来用,所以没什么好说的。

  1. String参数的传递
    Java的String和C++的string是不能对等起来的,所以处理起来比较麻烦。先看一个例子,
    //*******
    class Prompt {

// native method that prints a prompt and reads a line
private native String getLine(String prompt);

public static void main(String args[]) {
Prompt p = new Prompt();
String input = p.getLine(“Type a line: “);

System.out.println(“User typed: ” + input);
}

static {
System.loadLibrary(“Prompt”);
}
}

//*******

在这个例子中,我们要实现一个native方法
String getLine(String prompt);
读入一个String参数,返回一个String值。
通过执行javah -jni得到的头文件是这样的

//*******

include

ifndef _Included_Prompt

define _Included_Prompt

ifdef __cplusplus

extern “C” {

endif

JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject this, jstring prompt);

ifdef __cplusplus

}

endif

endif

//*******

jstring是JNI中对应于String的类型,但是和基本类型不同的是,jstring不能直接当作C++的string用。如果你用
cout << prompt << endl;
编译器肯定会扔给你一个错误信息的。
其实要处理jstring有很多种方式,这里只讲一种我认为最简单的方式,看下面这个例子,

//*******

include “Prompt.h”

include

JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
{
const char* str;
str = env->GetStringUTFChars(prompt, false);
if(str == NULL) {
return NULL; /* OutOfMemoryError already thrown */
}

std::cout << str << std::endl;

env->ReleaseStringUTFChars(prompt, str);
// return a string
char* tmpstr = “return string succeeded”;
jstring rtstr = env->NewStringUTF(tmpstr);
return rtstr;
}
//*******

在上面的例子中,作为参数的prompt不能直接被C++程序使用,先做了如下转换
str = env->GetStringUTFChars(prompt, false);
将jstring类型变成一个char*类型。
返回的时候,要生成一个jstring类型的对象,也必须通过如下命令,
jstring rtstr = env->NewStringUTF(tmpstr);
这里用到的GetStringUTFChars和NewStringUTF都是JNI提供的处理String类型的函数,还有其他的函数这里就不一一列举了。

/******************************************/
JNI编程系列之中级篇(下)
编程技术 2008-12-06 23:44 阅读7 评论0
字号: 大 中 小
3. 数组类型的传递
和String一样,JNI为Java基本类型的数组提供了j*Array类型,比如int[]对应的就是jintArray。来看一个传递int数组的例子,Java程序就不写了,
//*********
JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr)
{
jint *carr;
carr = env->GetIntArrayElements(arr, false);
if(carr == NULL) {
return 0; /* exception occurred */
}

jint sum = 0;
for(int i=0; i<10; i++) {
    sum += carr[i];

}

env->ReleaseIntArrayElements(arr, carr, 0);
return sum;

}
//*********

这个例子中的GetIntArrayElements和ReleaseIntArrayElements函数就是JNI提供用于处理int数组的函数。如果试图用arr[i]的方式去访问jintArray类型,毫无疑问会出错。JNI还提供了另一对函数GetIntArrayRegion和ReleaseIntArrayRegion访问int数组,就不介绍了,对于其他基本类型的数组,方法类似。

  1. 二维数组和String数组
    在JNI中,二维数组和String数组都被视为object数组,因为数组和String被视为object。仍然用一个例子来说明,这次是一个二维int数组,作为返回值。

//*********
JNIEXPORT jobjectArray JNICALL Java_ObjectArrayTest_initInt2DArray(JNIEnv *env, jclass cls, int size)
{
jobjectArray result;
jclass intArrCls = env->FindClass(“[I”);
result = env->NewObjectArray(size, intArrCls, NULL);
for (int i = 0; i < size; i++) {
jint tmp[256]; /* make sure it is large enough! */
jintArray iarr = env->NewIntArray(size);
for(int j = 0; j < size; j++) {
tmp[j] = i + j;
}
env->SetIntArrayRegion(iarr, 0, size, tmp);
env->SetObjectArrayElement(result, i, iarr);
env->DeleteLocalRef(iarr);
}
return result;
}
//*********

上面代码中的第三行,
jobjectArray result;
因为要返回值,所以需要新建一个jobjectArray对象。
jclass intArrCls = env->FindClass(“[I”);
是创建一个jclass的引用,因为result的元素是一维int数组的引用,所以intArrCls必须是一维int数组的引用,这一点是如何保证的呢?注意FindClass的参数”[I”,JNI就是通过它来确定引用的类型的,I表示是int类型,[标识是数组。对于其他的类型,都有相应的表示方法,
Z boolean
B byte
C char
S short
I int
J long
F float
D double
String是通过“Ljava/lang/String;”表示的,那相应的,String数组就应该是“[Ljava/lang/String;”。
还是回到代码,
result = env->NewObjectArray(size, intArrCls, NULL);
的作用是为result分配空间。
jintArray iarr = env->NewIntArray(size);
是为一维int数组iarr分配空间。
env->SetIntArrayRegion(iarr, 0, size, tmp);
是为iarr赋值。
env->SetObjectArrayElement(result, i, iarr);
是为result的第i个元素赋值。
通过上面这些步骤,我们就创建了一个二维int数组,并赋值完毕,这样就可以做为参数返回了。
如果了解了上面介绍的这些内容,基本上大部分的任务都可以对付了。虽然在操作数组类型,尤其是二维数组和String数组的时候,比起在单独的语言中编程要麻烦,但既然我们享受了跨语言编程的好处,必然要付出一定的代价。
有一点要补充的是,本文所用到的函数调用方式都是针对C++的,如果要在C中使用,所有的env->都要被替换成(*env)->,而且后面的函数中需要增加一个参数env,具体请看一下jni.h的代码。另外还有些省略的内容,可以参考JNI的文档:Java Native Interface 6.0 Specification,在JDK的文档里就可以找到。如果要进行更深入的JNI编程,需要仔细阅读这个文档。接下来的高级篇,也会讨论更深入的话题。

Chap13:JNI编程系列之高级篇

编程技术 2008-12-10 17:08 阅读6 评论0
字号: 大 中 小
在本篇中,将会涉及关于JNI编程更深入的话题,包括:在native方法中访问Java类的域和方法,将Java中自定义的类作为参数和返回值传递等等。了解这些内容,将会对JNI编程有更深入的理解,写出的程序也更清晰,易用性更好。

  1. 在一般的Java类中定义native方法
    在前两篇的例子中,都是将native方法放在main方法的Java类中,实际上,完全可以在任何类中定义native方法。这样,对于外部来说,这个类和其他的Java类没有任何区别。

  2. 访问Java类的域和方法
    native方法虽然是native的,但毕竟是方法,那么就应该同其他方法一样,能够访问类的私有域和方法。实际上,JNI的确可以做到这一点,我们通过几个例子来说明,

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

Chap14:如何在C/C++中调用Java

  
作者:刘冬 发文时间:2003.02.17
Java跨平台的特性使Java越来越受开发人员的欢迎,但也往往会听到不少的抱怨:用Java开发的图形用户窗口界面每次在启动的时候都会跳出 一个控制台窗口,这个控制台窗口让本来非常棒的界面失色不少。怎么能够让通过Java开发的GUI程序不弹出Java的控制台窗口呢?其实现在很多流行的 开发环境例如JBuilder、Eclipse都是使用纯Java开发的集成环境。这些集成环境启动的时候并不会打开一个命令窗口,因为它使用了 JNI(Java Native Interface)的技术。通过这种技术,开发人员不一定要用命令行来启动Java程序,可以 通过编写一个本地GUI 程序直接启动Java程序,这样就可避免另外打开一个命令窗口,让开发的Java程序更加专业。

JNI允许运行在虚拟机的Java程序能够与其它语言(例如C和C++)编写的程序或者类库进行相互间的调用。同时JNI提供的一整套的API,允许将Java虚拟机直接嵌入到本地的应用程序中。图1是Sun站点上对JNI的基本结构的描述。

图1 JNI基本结构描述图

本文将介绍如何在C/C++中调用Java方法,并结合可能涉及到的问题介绍整个开发的步骤及可能遇到的难题和解决方法。本文所采用的工具是 Sun公司创建的 Java Development Kit (JDK) 版本 1.3.1,以及微软公司的Visual C++ 6开发环境。

1.环境搭建

为了让本文以下部分的代码能够正常工作,我们必须建立一个完整的开发环境。首先需要下载并安装JDK 1.3.1,其下载地址为“http://java.sun.com”。假设安装路径为C:\JDK。下一步就是设置集成开发环境,通过Visual C++ 6的菜单Tools→Options打开选项对话框如图2。

图2 设置集成开发环境图

将目录C:\JDK\include和C:\JDK\include\win32加入到开发环境的Include Files目录中,
同时将C: \JDK\lib目录添加到开发环境的Library Files目录中。这三个目录是JNI定义的一些常量、结构及方法的头文件和库文件。

集成开发环境 已经设置完毕,同时为了执行程序需要把Java虚拟机所用到的动态链接库所在的目录C:\JDK \jre\bin\classic设置到系统的Path 环境变量中。这里需要提出的是,某些开发人员为了方便直接将JRE所用到的DLL文件直接拷贝到系统目录下。这样做是不行的,将导致初始化Java虚拟机 环境失败(返回值-1),原因是Java虚拟机是以相对路径来寻找所用到的库文件和其它一些相关文件的。

至此整个JNI的开发环境设置完毕,为了让此次 JNI旅程能够顺利进行,还必须先准备一个Java类。在这个类中将用到Java中几乎所有有代表性的属性及方法,如静态方法与属性、数组、异常抛出与捕 捉等。我们定义的Java程序(Demo.java)如下,本文中所有的代码演示都将基于该Java 程序,代码如下:

package jni.test;
/**
* 该类是为了演示JNI如何访问各种对象属性等
* @author liudong
*/
public class Demo {
//用于演示如何访问静态的基本类型属性
public static int COUNT = 8;
//演示对象型属性
public String msg;
private int[] counts;
public Demo() {
this(“缺省构造函数”);
}
/**
* 演示如何访问构造器
*/
public Demo(String msg) {
System.out.println(“:” + msg);
this.msg = msg;
this.counts = null;
}
/**
* 该方法演示如何访问一个访问以及中文字符的处理
*/
public String getMessage() {
return msg;
}
/**
* 演示数组对象的访问
*/
public int[] getCounts() {
return counts;
}
/**
* 演示如何构造一个数组对象
*/
public void setCounts(int[] counts) {
this.counts = counts;
}
/**
* 演示异常的捕捉
*/
public void throwExcp() throws IllegalAccessException {
throw new IllegalAccessException(“exception occur.”);
}
}

2.初始化虚拟机
本地代码在调用Java方法之前必须先加载Java虚拟机,而后所有的Java程序都在虚拟机中执行。
为了初始化Java虚拟机,JNI 提供了 一系列的接口函数Invocation API。通过这些API可以很方便地将虚拟机加载到内存中。创建虚拟机可以用函 数 jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args)。对于这个函数有一点 需要注意的是,在JDK 1.1中第三个参数总是指向一个结构JDK1_ 1InitArgs,这个结构无法完全在所有版本的虚拟机中进行无缝移植。在 JDK 1.2中已经使用了一个标准的初始化结构JavaVMInitArgs来替代JDK1_1InitArgs。下面我们分别给出两种不同版本的示例 代码。

在JDK 1.1初始化虚拟机:

include

include

include

include

include “Invoker.h”

include “invoker_include.h”

JavaVM * jvm;
JNIEnv * static_env;

jobject * jObject; // 线程间公用,必须使用 global reference
jclass c; // 必须使用 global reference
jmethodID m; // 必须使用 global reference

/*******************
* Class: Invoker
* Method: register
* Signature: ()V
*******************/
JNIEXPORT void JNICALL Java_Invoker_register (JNIEnv *env, jobject arg)
{
jObject = arg;

// printf(“object: %x, %x. \n”, &arg, &jObject);
printf(“[main] Invoker registered. \n”);

jclass bgpClass = (*env)->GetObjectClass(env, arg);
jmethodID methodId = (*env)->GetMethodID(env, bgpClass, “invoke”, “()V”);
printf(“[main] -class: %d, method: %d \n”, bgpClass, methodId);

(*env)->CallVoidMethod(env, arg, methodId);

// Global reference
(*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); // 手动销毁 global reference
(*env)->DeleteGlobalRef(env, m); // 手动销毁 global reference
(*env)->DeleteGlobalRef(env, jObject);
(*jvm)->DetachCurrentThread(jvm); // 销毁线程
(*jvm)->DestroyJavaVM(jvm); // ?销毁虚拟机
}

// Test method
JNIEXPORT void JNICALL Java_Invoker_println (JNIEnv *env, jobject obj, jstring string)
{
const char *str = (*env)->GetStringUTFChars(env, string, 0);
printf(“[main] %s\n”,str);
(*env)->ReleaseStringUTFChars(env, string, str);
}

// Callback method 回调函数
int invoke_java_method ()
{
(jvm)->AttachCurrentThread(jvm, (void*)&static_env, 0); // 获得当前线程可以使用的 JNIEnv * 指针
(*static_env)->CallVoidMethod(static_env, jObject, m); // 调用 Java 方法

printf(“[callback] java method invoked, invoker class: %x … \n”, &jObject);
}

chap 17:当JNI遇到多线程--java对象如何被C++中的多个线程访问?

java中要访问C++代码时, 使用JNI是唯一选择. 然而,在多线程的情况下, 可能出现以下问题:
问题描述:
一个java对象通过JNI调用DLL中一个send()函数向服务器发送消息,不等服务器消息到来就立即返回.同时把JNI接口的指针JNIEnv *env,和jobject obj保存在DLL中的变量里.
一段时间后,DLL中的消息接收线程接收到服务器发来的消息,并试图通过保存过的env和obj来调用先前的java对象的方法来处理此消息.
然而,JNI文档上说,JNI接口的指针JNIEnv*不能在c++的线程间共享,在我的程序中,如果接收线程试图调用java对象的方法,程序会突然退出.
不知道有没有方法突破JNI接口的指针不能在多个c++线程中共享的限制?
解决办法:
http://java.sun.com/docs/books/jni/html/pitfalls.html#29161 提到,
JNI接口指针不可为多个线程共用,但是java虚拟机的JavaVM指针是整个jvm公用的. 于是,在DLL中可以调用:
static JavaVM* gs_jvm;
env->GetJavaVM(&gs_jvm); 来获取JavaVM指针.获取了这个指针后,在DLL中的另一个线程里,可以调用:
JNIEnv *env;
gs_jvm->AttachCurrentThread((void **)&env, NULL);
来将DLL中的线程 “attached to the virtual machine”(不知如何翻译…),同时获得了这个线程在jvm中的 JNIEnv指针.
由于我需要做的是在DLL中的一个线程里改变某个java对象的值,所以,还必须获取那个java对象的jobject指针.同 JNIEnv 指针一样,jobject指针也不能在多个线程中共享. 就是说,不能直接在保存一个线程中的jobject指针到全局变量中,然后在另外一个线程中使用它.幸运的是,可以用
gs_object=env->NewGlobalRef(obj);
来将传入的obj保存到gs_object中,从而其他线程可以使用这个gs_object来操纵那个java对象了.
示例代码如下:
(1)java代码:
//file name: Test.java
import java.io.*;
class Test implements Runnable
{
public int value = 0;
private Thread tx=null;

public Test()
{
tx=new Thread(this,”tx”);
}

static
{
System.loadLibrary(“Test”);
}

public native void setEnev();

public static void main(String args[])
{
Test t = new Test();
t.setEnev();
System.out.println(“ok in java main”);
t.tx.start ();

try
{
Thread.sleep(10000000);
}catch(Exception e)
{
System.out.println(“error in main”);
}
}

public void run()
{
try
{
while(true)
{
Thread.sleep(1000);
System.out.println(value);
}
}catch(Exception e)
{
System.out.println(“error in run”);
}
}
}

(2) DLL代码:

//cpp file name: Test.cpp:

include “test.h”

include

include

include

ifndef _Included_com_testJni_Circle

define _Included_com_testJni_Circle

ifdef __cplusplus

extern “C” {

endif

/*
* Class: com_testJni_Circle
* Method: cAreas
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas
(JNIEnv *, jobject, jint);

ifdef __cplusplus

}

endif

endif

如果在本地化方法声明中,方法cAreas ()声明为static类型,则与之相对应的Java_com_testJni_Circle_cAreas()函数中的第二个参数类型为jclass。也就是
JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas(JNIEnv *env, jclass newCircle,jint radius)。
这里JNIEXPORT和JNICALL都是JNI的关键字,其实是一些宏(具体参看jni_md.h文件)。
从以上头文件中,可以看出函数名生成规则为:Java[ 包名]类名方法名[ 函数签名](其中[ ]是可选项),均以字符下划线( _ )分割。如果是无包的情况,则不包含[ _包名]选项。如果本地化方法中有方法重载,则在该函数名最后面追加函数签名,也就是Signature对应的值,函数签名参见表一。

函数签名 Java类型
V void
Z boolean
B byte
C char
S short
I int
J long
F float
D double
L fully-qualified-class ; fully-qualified-class
[ type type[]
( arg-types ) ret-type method type
表一函数签名与Java类型的映射
在具体实现的时候,我们只关心函数原型:
JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas(JNIEnv *, jobject, jint);
现在就让我们开始激动人心的一步吧 : ) 。启动VC集成开发环境,新建一工程,在project里选择win32 Dynamic-link Library,输入工程名,然后点击ok,接下去步骤均取默认(图三)。如果不取默认,生成的工程将会有DllMain ()函数,反之将无这个函数。我在这里取的是空。

图三 新建DLL工程
然后选择菜单File->new->Files->C++ Source File,生成一个空.cpp文件,取名为CCircle。与3.1中System.loadLibrary(“CCircle”);参数保持一致。将JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas(JNIEnv , jobject, jint);拷贝到CPP文件中,并包含其头文件。
对应的CCircle.cpp内容如下:

include

include”com_testJni_Circle.h”

include”windows.h”

JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas(JNIEnv *env, jobject newCircle,jint radius)
{
//调用求圆面积的Circle.dll
typedef void (*PCircle)(int radius);
HINSTANCE hDLL;
PCircle Circle;
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中如下定义,

define JNIEXPORT __declspec(dllexport),可见JNI默认的导出函数使用第二种。使用第二种方式产生的导出函数名会根据编译器发生变化,在有的情况下会发生找不到导出函数的问题(我们在java控制台程序中调用很正常,但把它移植到JSP页面时,就发生了该问题,JVM开始崩溃,百思不得其解,后来加入一个.def文件才解决问题)。其实在《windows 核心编程》一书中,第19.3.2节就明确指出创建用于非Visual C++工具的DLL时,建议加入一个def文件,告诉Microsoft编译器输出没有经过改变的函数名。因此最好采用第一种方法,定义一个.def文件来指明导出函数。本例中可以新建一个CCircle.def文件,内容如下:

; CCircle.def : Declares the module parameters for the DLL.

LIBRARY “CCircle”
DESCRIPTION ‘CCircle Windows Dynamic Link Library’

EXPORTS
; Explicit exports can go here
Java_com_testJni_Circle_cAreas
现在开始对所写的程序进行编译。选择build->rebuild all对所写的程序进行编译。点击build->build CCirclee.DLL生成DLL文件。
也可以用命令行cl来编译。语法格式参见JDK文档JNI部分。
再次强调(曾经为这个东西大伤脑筋):DLL放置地方
1) 当前目录。
2) Windows的系统目录及Windows目录
3) 放在path所指的路径中
4) 自己在path环境变量中设置一个路径,要注意所指引的路径应该到.dll文件的上一级,如果指到.dll,则会报错。
下面就开始测试我们的所写的DLL吧(假设DLL已放置正确)。
import com.testJni.Circle;
public class test
{
public static void main(String argvs[])
{
Circle myCircle;
myCircle = new Circle();
myCircle.cAreas(2);
}
}
编译,运行程序,将会弹出如下界面:

图五 运行结果
以上是我们通过JNI方法调用的一个简单程序。实际情况要比这复杂的多。
现在开始来讨论JNI中参数的情况,我们来看一个程序片断。
实例二:
JNIEXPORT jstring JNICALL Java_MyNative_cToJava
(JNIEnv *env, jclass obj)
{
jstring jstr;
char str[]=”Hello,word!\n”;
jstr=env->NewStringUTF(str);
return jstr;
}
在C和Java编程语言之间传送值时,需要理解这些值类型在这两种语言间的对应关系。这些都在头文件jni.h中定义,用typedef语句声明了这些类在目标平台上的代价类。头文件也定义了常量如:JNI_FALSE=0 和JNI_TRUE=1;表二和表三说明了Java类型和C类型之间的映射关系。
Java语言 C/C++语言 bit位数
boolean jboolean 8 unsigned
byte jbyte 8
char jchar 16 unsigned
short jshort 16
int jint 32
long jlong 64
float jfloat 32
double jdouble 64
void void 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互传参数需要自己在编程过程中仔细摸索与体味。
四、 C/C++访问Java成员变量和成员方法
我们修改3.1中的Java程序声明,加入如下代码:
private int circleRadius;
public Circle()
{
circleRadius=0;
}
public void setCircleRadius(int radius)
{
circleRadius=radius;
}
public void javaAreas()
{
float PI = 3.14f;
if(circleRadius<=0)
{
System.out.println (“error!”);
}
else
{
System.out.println (PI*circleRadius*circleRadius);
}
}
在C++程序中访问Circle类中的private私有成员变量circleRadius,并设置它的值,同时调用Java方法javaAreas()。在函数Java_com_testJni_Circle_cAreas()中加入如下代码:
jclass circle;
jmethodID AreasID;
jfieldID radiusID;
jint newRadius=5;
circle = env->GetObjectClass(newCircle);//get current class
radiusID=env->GetFieldID(circle,”circleRadius”,”I”);//get field ID
env->SetIntField(newCircle,radiusID,newRadius);//set field value
AreasID=env->GetMethodID(circle,”javaAreas”,”()V”);//get method ID
env->CallVoidMethod(newCircle,AreasID,NULL);//invoking method
在C++代码中,创建、检查及更新Java对象,首先要得到该类,然后再根据类得到其成员的ID,最后根据该类的对象,ID号调用成员变量或者成员方法。
得到类,有两个API函数,分别为FindClass()和GetObjectClass();后者顾名思义用于已经明确知道其对象,然后根据对象找类。前者用于得到没有实例对象的类。这里也可以改成circle = env-> FidnClass(“com/testJni/Circle”);其中包的分隔符用字符” /”代替。如果已知一个类,也可以在C++代码中创建该类对象,其JNI函数为NewObject();示例代码如下:
jclass circle =env->FindClass(“com/testJni/ Circle “);
jmethodID circleID=env->GetMethodID(circle,””,”()V”);//得到构造函数的ID
jobject newException=env->NewObject(circle, circleID,NULL);
得到成员变量的ID,根据其在Java代码中声明的类型不同而不同。具体分为两大类:非static型和static型,分别对应GetFieldID()和GetStaticFieldID()。同时也可以获得和设置成员变量的值,根据其声明的type而变化,获得其值的API函数为:GettypeField()和GetStatictypeField();与之相对应的设置其值的函数为SettypeField()和SetStatictypeField();在本例中,成员变量circleRadius声明成int型,则对应的函数分别为GetIntField()和SetIntField();
其实JNI API函数名是很有规律的,从上面已窥全貌。获得成员方法的ID也是同样的分类方法。具体为GetMethodID()和GetStaticMethodID()。调用成员方法跟获得成员变量的值相类似,也根据其方法返回值的type不同而不同,分别为CalltypeMethod()和CallStatictypeMethod()。对于返回值为void的类型,其相应JNI函数为CallVoidMethod();
以上获得成员ID函数的形参均一致。第一个参数为jclass,第二个参数为成员变量或方法,第三个参数为该成员的签名(签名可参见表一)。但调用或设置成员变量或方法时,第一个参数为实例对象(即jobject),其余形参与上面相同。
特别要注意的是得到构造方法的ID时,第二个参数不遵循上面的原则,为jmethodID constructorID = env->GetMethodID(jclass, “”,” 函数签名”);
从上面代码中可以看出,在C++中可以访问java程序private类型的变量,严重破坏了类的封装原则。从而可以看出其不安全性。
五、 异常处理
本地化方法稳定性非常差,调用任何一个JNI函数都会出错,为了程序的健壮性,非常有必要在本地化方法中加入异常处理。我们继续修改上面的类。
我们声明一个异常类,其代码如下:
package com.testJni;
import com.testJni.*;
public class RadiusIllegal extends Exception
{
protected String MSG=”error!”;
public RadiusIllegal(String message)
{
MSG=message;
}
public void print()
{
System.out.println(MSG);
}
}
同时也修改Circle.java中的方法,加入异常处理。
public void javaAreas() throws RadiusIllegal //修改javaAreas(),加入异常处理
{
float PI = 3.14f;
if(circleRadius<=0)
{
throw new RadiusIllegal(“warning:radius is illegal!”);
}
else
{
System.out.println (PI*circleRadius*circleRadius);
}
}
public native void cAreas(int radius) throws RadiusIllegal; //修改cAreas (),加入异常处理
修改C++代码中的函数,加入异常处理,实现Java和C++互抛异常,并进行异常处理。
JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas(JNIEnv *env, jobject newCircle,jint radius)
{
//此处省略部分代码
radiusIllegal=env->FindClass(“com/testJni/RadiusIllegal”);//get the exception class
if((exception=env->ExceptionOccurred())!=NULL)
{
cout<<”errors in com_testJni_RadiusIllegal”<

=====================================================================
利用jawin完成调用window中dll的调用
http://hi.baidu.com/linjk03/blog/item/2c506a349988d6bfd0a2d34c.html
最近由于项目的特殊需求,我们必须在程序调用window的dll。
开始我们用jni,后来由于调用的dll太多,而且很烦琐。所以,我们决定用开源的jawin调用。
jawin 可以对dll中的方法进行调用,也可以调用com中的方法.内部还提供了一个工具,直接对 com组件导出成 java的类,个人认为很方便。

下面是我们作的一个测试,很顺利便通过了。
1、下载jawin:http://jawinproject.sourceforge.net/
2、配置:
》将jawin.jar放于%JAVA_HOME%\jre\lib\ext下 。
》将jawin.dll放于c:\winnt\system32下。否则将出现错误:COMException : no jawin in java.library.path;
也可将jawin.dll放于每个项目目录下。
》至此在Editplus中调试Jawin/NJawin的例子,可以通过。 而在Eclipse中有时还会出上面的错误:COMException : no jawin in java.library.path。
》在Eclipse中,菜单->window->preference->Java->installed JREs 将原来的remove,重新建一个指到你的java sdk目录。
》 ok了。
3、程序测试:
》调用 dll,dll 的方式不需要导出了,直接调用就可以了,下面是下载的包中提供的一个例子:
》我在win2000下,测试通过。
/*
* Created on Dec 22, 2005
*
*/
import org.jawin.FuncPtr;
import org.jawin.ReturnFlags;
/**
* @author gf mail to gf@163.com
*
* TODO To change the template for this generated type comment go to
* Window - Preferences - Java - Code Style - Code Templates
*/
public class GfJawinTest {
public static void main(String[] args) {
try {
FuncPtr msgBox = new FuncPtr(“USER32.DLL”, “MessageBoxW”);

                 msgBox.invoke_I(0, "Hello From a DLL", "From Jawin", 0, ReturnFlags.CHECK_NONE);
          } catch (Exception e) {
                 e.printStackTrace();
          }
   }

}

static int32_t readInputStreamData(int32_t handle, uint8_t* buf, int32_t bufLen)
{
JNIEnv* env;
jobject* pInputStream;
int32_t len;
DrmData* p;
jclass cls;
jmethodID mid;
jbyteArray tmp;
int tmpLen;
jbyte* pNativeBuf;

p = (DrmData *)handle;

if (NULL == p || NULL == buf || bufLen <- 0)
    return 0;

env = p->env;
pInputStream = p->pInData;
len = p->len;

if (NULL == env || p->len <= 0 || NULL == pInputStream)
    return 0;

cls = (*env)->GetObjectClass(env, *pInputStream);
mid = (*env)->GetMethodID(env, cls, "read", "([BII)I");
tmp = (*env)->NewByteArray(env, bufLen);
bufLen = (*env)->CallIntMethod(env, *pInputStream, mid, tmp, 0, bufLen);

(*env)->DeleteLocalRef(env, cls);

if (-1 == bufLen)
    return -1;

pNativeBuf = (*env)->GetByteArrayElements(env, tmp, NULL);
memcpy(buf, pNativeBuf, bufLen);
(*env)->ReleaseByteArrayElements(env, tmp, pNativeBuf, 0);
(*env)->DeleteLocalRef(env, tmp);

return bufLen;

}

Ch22:用Java代码处理本地对象的事件
当您需要使用以其他语言编写的对象时,本地事件源和 Java? 侦听器之间的通信可能需要一些小技巧 —— 尤其是在多线程环境中。本文通过使用一种透明处理从本地代码到 JVM 的事件通信的设计模式,帮助您有效地处理传统的本地库。

   在面向对象系统中,对象可以触发一组事件。Java 编程语言为定义基于观察者设计模式(Observer design pattern)的事件侦听器提供了支持,但当您需要使用以其他语言编写的对象时,这还不够。使用 Java Native Interface (JNI) 在本地事件源和 Java 侦听器之间进行通信,可能需要一些技巧,尤其是有多线程环境中。在本文中,我们描述了一种透明处理从本地代码到 JVM 的事件通信的设计模式。您可以使用这种设计来提供到遗留本地应用程序的 Java 接口,或者构建带 Java 侦听器的本地应用程序。
1.观察者设计模式
观察者设计模式定义了事件侦听器与事件创建者之间的多对一依赖关系。当事件创建者触发一个事件时,其所有侦听器接收到该事件的通知。由于事件创建者和侦 听器是无关的,您可以单独使用或修改它们。这种设计模式是事件驱动编程的核心,被广泛用于 GUI 框架,比如 Swing 和 SWT。

如果整个应用程序都使用 Java 编程语言编写,实现观察者设计模式相当简单。图 1 中的类图给出了一个例子:

图 1. 观察者设计模式

Java 本地应用程序侦听器

不过,在某些情况下,我们想要让本地应用程序支持 Java 侦听器。大量应用程序(包括应用程序入口点)可能以本地代码编写,而应用程序以与其用户界面交互的方式生成事件。在这种情形下,支持基于Java 用户界面的最佳方式是让 Java 类将自身注册为应用程序生成的各种事件的侦听器。简而言之,通过支持以 Java 语言编写的侦听器,可以获得支持 Java 的 本地应用程序。

图 2 所示的类图给出了一个示例场景。CEventSource 类用 C++ 语言编写。它使用 addMouseDownListener() 和 removeMouseDownListener() 让侦听器注册和取消注册其 “鼠标按下” 事件。它想要所有侦听器实现 IMouseDownListener 接口。

图 2. 示例场景的类图

   注意,IMouseDownListener 是一个 C++ 抽象类。那么,Java 类如何注册事件才不会引入 Java 侦听器和 CEventSource 类之间的编译时绑定呢?在其注册事件后,CEventSource 类如何调用 Java 类中的方法呢?这正是 Java Invocation API 的用武之地。

2.Java Invocation API
Invocation API 让您可以将 JVM 加载到一个本地应用程序中,而不必显式地链接 JVM 源。 通过在 jvm.dll 中调用一个函数,可以创建一个 JVM,jvm.dll 还将当前本地线程连接到 JVM。然后,您可以在 JVM 中从本地线程调用所有 Java 方法。

然而,Invocation API 无法彻底解决问题。您不希望 CEventSource 类具有与 Java 侦听器的编译时依赖关系。另外,Java 侦听器不应该承担使用 JNI 来注册带 CEventSource 的侦听器的责任。

3.代理设计模式

通过使用代理设计模式(Proxy design pattern),可以避免这一弊端。通常来说,代理是另一个对象的占位符。客户端对象可以处理代理对象,而代理封装了所有使用本地方法的细节。代理模式的细节显示在图 3 中:

图 3. 代理设计模式示例

   EventSourceProxy 是 CEventSource 类的代理。要将其自身注册为一个侦听器,客户端应该实现 IJMouseDownListener 接口。该接口类似于 IMouseDownListener,但它是用 Java 代码编写的。当第一个客户端调用其 addMouseDownListener() 方法时,它使用 registerListener() 本地方法来将其自身注册为一个带 CEventSource 的侦听器。registerListener() 方法用 C++ 语言来实现。它创建一个 JMouseDownListener 对象,并将其注册为一个带 CEventSource 的侦听器。当 JMouseDownListener 的 onMouseDown 事件被触发时,JMouseDownListener 中的 onMouseDownListener() 方法使用 Invocation API 来通知 EventSourceProxy。

   EventSourceProxy 还维持一组使用它来注册的侦听器。无论其 onMouseDown 何时被触发,它将通知该组中的所有侦听器。注意,即使针对该事件存在多个 Java 侦听器,只有代理的一个实例被注册为具有 CEventSource。代理将 onMouseDown 事件委托给它的所有侦听器。这防止了本地代码和 Java 代码之间不必要的上下文切换。

4.多线程问题

  本地方法接收 JNI 接口指针作为一个参数。但是,一个想要将事件委托回其关联 Java 代理的本地侦听器没有现成的 JNI 接口指针。一旦获得 JNI 接口指针,应该将其保存起来以便后续使用。

  JNI 接口指针只在当前线程中有效。实现 JNI 的 JVM 可以在 JNI 接口指针指向的区域中分配和存储本地线程数据。这意味着您也需要以本地线程数据保存 JNI 接口指针。

  JNI 接口指针可以两种方式获得:
一旦线程使用 JNI_CreateJavaVM 创建了 JVM,JNI 将接口指针值放在由第二个参数指定的位置。然后该值可以保存在本地线程区域中。
如果 JVM 已由进程中某个其他线程创建,当前线程可以调用 AttachCurrentThread。JNI 将接口指针值放在由第一个参数指定的位置。
  但是这还没有完。需要记住的是,程序是以 C/C++ 语言编写的,因此,无法使用自动垃圾回收,因为程序不是使用 Java 语言编写的。一旦线程完成 JNI 调用,它需要通过调用 DetachCurrentThread 来释放接口指针。如果未做此调用并且线程存在,进程将无法正常终止。相反,它将一直等待现在已不存在的线程以 DestroyJavaVM 调用的方式从 JVM 中离开。

5.环境设置

  公共接口

   IMouseDownListener 和 IEventSource 接口定义在 common.h 中。IMouseDownListener 只有一个方法:onMouseDown()。该方法接收鼠标单击的屏幕位置。IEventSource 接口包含了 addMouseDownListener() 和 removeMouseDownListener() 方法,用于注册和取消注册侦听器。

  Java Invocation API 的帮助例程

  有 7 个必需的常用工具方法可用于简化 Java Invocation API 的使用,它们定义在 Jvm.h 中,在 Jvm.cpp 中实现:
CreateJavaObject() 创建一个 Java 对象,给出其类名和针对本地 IEventSource 的句柄。这个创建好的 Java 对象将用于侦听来自该本地句柄的事件。该句柄通过其构造方法传递给对象。
ReleaseJObject() 调用 Java 对象的 release() 方法。该方法用于从 EventSourceProxy 取消注册对象的侦听器。
DetachThread() 从 JVM 分离当前线程(如果当前线程连接到 JVM)。当线程正被连接时,该调用对于释放特定于线程的、已分配给 JNI 的资源是很必要的。
  其余方法都自己附带有解释:
CreateJVM()
DestroyJVM()
GetJVM()
GetJNIEnv()
CJvm 类还在特定于线程的位置保存一个 JNI 环境指针。这是很有必要的,因为 JNI 环境指针是线程相关的。

  本地事件源

  在 EventSource.h 和 EventSource.cpp 中,CEventSource 是一个简单而直观的 IEventSource 接口的实现。

  本地事件侦听器

  在 MouseDownListener.h 和 MouseDownListener.cpp 中,CMouseDownListener 是 IMouseDownListener 接口的实现。该本地侦听器仅出于解释目的而编写。

  入口点

  main.cpp 包含 main() 和 ThreadMain()。 main() 创建一个本地 EventSource、一个本地侦听器和一个 Java 侦听器。然后创建线程,并在睡眠几秒后让它执行。最后,它释放 Java 侦听器并销毁 JVM。

  ThreadMain() 简单地触发一个事件,然后将自身从 JVM 分离出来。

Java 模块

  IJMouseDownListener.java 中的 IJMouseDownListener 只是本地接口针对 Java 平台的一个克隆。

   MouseDownListener 是 Java 中的一个示例侦听器,在 MouseDownListener.java 中实现。它在其构造方法中接收本地 EventSource 句柄。它定义了一个 release() 方法,该方法取消注册带 EventSourceProxy 的侦听器。

  EventSourceProxy 是一个用于来自本地模块的 EventSource 的占位符或代理项。它在 EventSourceProxy.java 中实现。它维持一个静态哈希表,以将一个代理映射到实际 EventSource。

   addMouseDownListener() 和 removeMouseDownListener() 允许您维持一个 Java 侦听器集合。单个本地 EventSource 可以有多个 Java 侦听器,但只有在必要时代理才注册/取消注册本地 EventSource。

  当从本地 EventSource 转发事件时,EventSourceProxy 的本地实现调用 fireMouseDownEvent()。该方法迭代 Java 侦听器的哈希集合,并通知它们。

  EventSourceProxy 的本地部分还维持一个到自身的全局引用。这对于稍后调用 fireMouseDownEvent() 是很必要的。

  构建并执行示例代码

  示例代码中的所有 Java 类都使用普通过程构建,无需特殊步骤。对于 EventSourceProxy 的本地实现,您需要使用 javah 生成头文件:
javah -classpath .\java\bin events.EventSourceProxy

   为了构建针对 Win32 平台的 C++ 模块,我们提供了 Microsoft Developer Studio 项目文件和 cpp.dsw 工作区。您可以打开工作区,简单地构建 main 项目。工作区中的所有项目都以适当的依赖关系相关联。确保您的 Developer Studio 可以找到 JNI 头和编译时 JNI 库。可以通过选择 Tools > Options > Directories 菜单项完成这一工作。

  构建成功之后,在可以执行示例程序之前,还需要完成几个步骤。

  首先,因为用于构建 Java 类并包含 JNI 头和库的 JDK 可能有针对 Java Invocation API 的运行时组件,例如 jvm.dll,您必需设置它。最简单的方法是更新 PATH 变量。

  其次,main 程序带有命令行参数,这些参数是简单的 JVM 参数。您需要至少传递两个参数给 JVM:

main.exe “-Djava.class.path=.\java\bin”
“-Djava.library.path=.\cpp\listener\Debug”

  得到的控制台输出如下:

In CMouseDownListener::onMouseDown
X = 50
Y = 100
In MouseDownListener.onMouseDown
X = 50
Y = 100

  正如您从控制台输出所看到的,Java 侦听器产生与出于解释目的而构建的本地侦听器相同的结果。

  结束语

   本文展示了如何为本地应用程序生成的事件注册一个 Java 类作为侦听器。通过使用观察者设计模式,您已经减少了事件源与侦听器之间的耦合。您还通过使用代理设计模式隐藏了来自 Java 侦听器的事件源的实现细节。您可以使用该设计模式组合来将一个 Java UI 添加到现有的本地应用程序。

Ch23: JNI and Thread
The Java virtual machine supports multiple threads of control concurrently executing in the same address space. This concurrency introduces a degree of complexity that you do not have in a single-threaded environment. Multiple threads may access the same objects, the same file descriptors–in short, the same shared resources–at the same time.
To get the most out of this section, you should be familiar with the concepts of multithreaded programming. You should know how to write Java applications that utilize multiple threads and how to synchronize access of shared resources. A good reference on multithreaded programming in the Java programming language is Concurrent Programming in JavaTM, Design Principles and Patterns, by Doug Lea (Addison-Wesley, 1997).
1.1 Constraints
There are certain constraints that you must keep in mind when writing native methods that are to run in a multithreaded environment. By understanding and programming within these constraints, your native methods will execute safely no matter how many threads simultaneously execute a given native method. For example:
A JNIEnv pointer is only valid in the thread associated with it. You must not pass this pointer from one thread to another, or cache and use it in multiple threads. The Java virtual machine passes a native method the same JNIEnv pointer in consecutive invocations from the same thread, but passes different JNIEnv pointers when invoking that native method from different threads. Avoid the common mistake of caching the JNIEnv pointer of one thread and using the pointer in another thread.
Local references are valid only in the thread that created them. You must not pass local references from one thread to another. You should always convert local references to global references whenever there is a possibility that multiple threads may use the same reference.
1.2 Monitor Entry and Exit
Monitors are the primitive synchronization mechanism on the Java platform. Each object can be dynamically associated with a monitor. The JNI allows you to synchronize using these monitors, thus implementing the functionality equivalent to a synchronized block in the Java programming language:
synchronized (obj) {
… // synchronized block
}
The Java virtual machine guarantees that a thread acquires the monitor associated with the object obj before it executes any statements in the block. This ensures that there can be at most one thread that holds the monitor and executes inside the synchronized block at any given time. A thread blocks when it waits for another thread to exit a monitor.
Native code can use JNI functions to perform equivalent synchronization on JNI references. You can use the MonitorEnter function to enter the monitor and the MonitorExit function to exit the monitor:
if ((*env)->MonitorEnter(env, obj) != JNI_OK) {
… /* error handling */
}
… /* synchronized block */
if ((*env)->MonitorExit(env, obj) != JNI_OK) {
… /* error handling */
};
Executing the code above, a thread must first enter the monitor associated with obj before executing any code inside the synchronized block. The Monitor-Enter operation takes a jobject as an argument and blocks if another thread has already entered the monitor associated with the jobject. Calling MonitorExit when the current thread does not own the monitor results in an error and causes an Illegal-MonitorStateException to be raised. The above code contains a matched pair of MonitorEnter and MonitorExit calls, yet we still need to check for possible errors. Monitor operations may fail if, for example, the underlying thread implementation cannot allocate the resources necessary to perform the monitor operation.
MonitorEnter and MonitorExit work on jclass, jstring, and jarray types, which are special kinds of jobject references.
Remember to match a MonitorEnter call with the appropriate number of MonitorExit calls, especially in code that handles errors and exceptions:
if ((*env)->MonitorEnter(env, obj) != JNI_OK) …;

if ((*env)->ExceptionOccurred(env)) {
… /* exception handling */
/* remember to call MonitorExit here */
if ((*env)->MonitorExit(env, obj) != JNI_OK) …;
}
… /* Normal execution path.
if ((*env)->MonitorExit(env, obj) != JNI_OK) …;
Failure to call MonitorExit will most likely lead to deadlocks. By comparing the above C code segment with the code segment at the beginning of this section, you can appreciate how much easier it is to program with the Java programming language than with the JNI. Thus, it is preferable to express synchronization constructs in the Java programming language. If, for example, a static native method needs to enter the monitor associated with its defining class, you should define a static synchronized native method as opposed to performing JNI-level monitor synchronization in native code.
1.3 Monitor Wait and Notify
The Java API contains several other methods that are useful for thread synchronization. They are Object.wait, Object.notify, and Object.notifyAll. No JNI functions are supplied that correspond directly to these methods because monitor wait and notify operations are not as performance critical as monitor enter and exit operations. Native code may instead use the JNI method call mechanism to invoke the corresponding methods in the Java API:
/* precomputed method IDs */
static jmethodID MID_Object_wait;
static jmethodID MID_Object_notify;
static jmethodID MID_Object_notifyAll;

void
JNU_MonitorWait(JNIEnv *env, jobject object, jlong timeout)
{
(*env)->CallVoidMethod(env, object, MID_Object_wait,
timeout);
}

void
JNU_MonitorNotify(JNIEnv *env, jobject object)
{
(*env)->CallVoidMethod(env, object, MID_Object_notify);
}

void
JNU_MonitorNotifyAll(JNIEnv *env, jobject object)
{
(*env)->CallVoidMethod(env, object, MID_Object_notifyAll);
}
We assume that the method IDs for Object.wait, Object.notify, and Object.notifyAll have been calculated elsewhere and are cached in the global variables. Like in the Java programming language, you can call the above monitor-related functions only when holding the monitor associated with the jobject argument.
1.4 Obtaining a JNIEnv Pointer in Arbitrary Contexts
We explained earlier that a JNIEnv pointer is only valid in its associated thread. This is generally not a problem for native methods because they receive the JNIEnv pointer from the virtual machine as the first argument. Occasionally, however, it may be necessary for a piece of native code not called directly from the virtual machine to obtain the JNIEnv interface pointer that belongs to the current thread. For example, the piece of native code may belong to a “callback” function called by the operating system, in which case the JNIEnv pointer will probably not be available as an argument.
You can obtain the JNIEnv pointer for the current thread by calling the AttachCurrentThread function of the invocation interface:
JavaVM jvm; / already set */

f()
{
JNIEnv *env;
(jvm)->AttachCurrentThread(jvm, (void *)&env, NULL);
… /* use env */
}
When the current thread is already attached to the virtual machine, Attach-Current-Thread returns the JNIEnv interface pointer that belongs to the current thread.
There are many ways to obtain the JavaVM pointer: by recording it when the virtual machine is created, by querying for the created virtual machines using JNI_GetCreatedJavaVMs, by calling the JNI function GetJavaVM inside a regular native method, or by defining a JNI_OnLoad handler. Unlike the JNIEnv pointer, the JavaVM pointer remains valid across multiple threads so it can be cached in a global variable.
Java 2 SDK release 1.2 provides a new invocation interface function GetEnv so that you can check whether the current thread is attached to the virtual machine, and, if so, to return the JNIEnv pointer that belongs to the current thread. GetEnv and AttachCurrentThread are functionally equivalent if the current thread is already attached to the virtual machine.
1.5 Matching the Thread Models
Suppose that native code to be run in multiple threads accesses a global resource. Should the native code use JNI functions MonitorEnter and MonitorExit, or use the native thread synchronization primitives in the host environment (such as mutex_lock on Solaris)? Similarly, if the native code needs to create a new thread, should it create a java.lang.Thread object and perform a callback of Thread.start through the JNI, or should it use the native thread creation primitive in the host environment (such as thr_create on Solaris)?
The answer is that all of these approaches work if the Java virtual machine implementation supports a thread model that matches that used by the native code. The thread model dictates how the system implements essential thread operations such as scheduling, context switching, synchronization, and blocking in system calls. In a native thread model the operating system manages all the essential thread operations. In a user thread model, on the other hand, the application code implements the thread operations. For example, the “Green thread” model shipped with JDK and Java 2 SDK releases on Solaris uses the ANSI C functions setjmp and longjmp to implement context switches.
Many modern operating systems (such as Solaris and Win32) support a native thread model. Unfortunately, some operating systems still lack native thread support. Instead, there may be one or many user thread packages on these operating systems.
If you write application strictly in the Java programming language, you need not worry about the underlying thread model of the virtual machine implementation. The Java platform can be ported to any host environment that supports the required set of thread primitives. Most native and user thread packages provide the necessary thread primitives for implementing a Java virtual machine.
JNI programmers, on the other hand, must pay attention to thread models. The application using native code may not function properly if the Java virtual implementation and the native code have a different notion of threading and synchronization. For example, a native method could be blocked in a synchronization operation in its own thread model, but the Java virtual machine, running in a different thread model, may not be aware that the thread executing the native method is blocked. The application deadlocks because no other threads will be scheduled.
The thread models match if the native code uses the same thread model as the Java virtual machine implementation. If the Java virtual machine implementation uses native thread support, the native code can freely invoke thread-related primitives in the host environment. If the Java virtual machine implementation is based on a user thread package, the native code should either link with the same user thread package or rely on no thread operations at all. The latter may be harder to achieve than you think: most C library calls (such as I/O and memory allocation functions) perform thread synchronization underneath. Unless the native code performs pure computation and makes no library calls, it is likely to use thread primitives indirectly.
Most virtual machine implementations support only a particular thread model for JNI native code. Implementations that support native threads are the most flexible, hence native threads, when available, are typically preferred on a given host environment. Virtual machine implementations that rely on a particular user thread package may be severely limited as to the type of native code with which they can operate.
Some virtual machine implementations may support a number of different thread models. A more flexible type of virtual machine implementation may even allow you to provide a custom thread model implementation for virtual machine’s internal use, thus ensuring that the virtual machine implementation can work with your native code. Before embarking on a project likely to require native code, you should consult the documentation that comes with your virtual machine implementation for thread model limitations.

8.4 Load and Unload Handlers
Load and unload handlers allow the native library to export two functions: one to be called when System.loadLibrary loads the native library, the other to be called when the virtual machine unloads the native library. This feature was added in Java 2 SDK release 1.2.
8.4.1 The JNI_OnLoad Handler
When System.loadLibrary loads a native library, the virtual machine searches for the following exported entry in the native library:
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved);
You can invoke any JNI functions in an implementation of JNI_Onload. A typical use of the JNI_OnLoad handler is caching the JavaVM pointer, class references, or method and field IDs, as shown in the following example:
JavaVM *cached_jvm;
jclass Class_C;
jmethodID MID_C_g;
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;
cached_jvm = jvm; /* cache the JavaVM pointer */

 if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_2)) {
     return JNI_ERR; /* JNI version not supported */
 }
 cls = (*env)->FindClass(env, "C");
 if (cls == NULL) {
     return JNI_ERR;
 }
 /* Use weak global ref to allow C class to be unloaded */
 Class_C = (*env)->NewWeakGlobalRef(env, cls);
 if (Class_C == NULL) {
     return JNI_ERR;
 }
 /* Compute and cache the method ID */
 MID_C_g = (*env)->GetMethodID(env, cls, "g", "()V");
 if (MID_C_g == NULL) {
     return JNI_ERR;
 }
 return JNI_VERSION_1_2;

}
The JNI_OnLoad function first caches the JavaVM pointer in the global variable cached_jvm. It then obtains the JNIEnv pointer by calling GetEnv. It finally loads the C class, caches the class reference, and computes the method ID for C.g. The JNI_OnLoad function returns JNI_ERR (§12.4) on error and otherwise returns the JNIEnv version JNI_VERSION_1_2 needed by the native library.
We will explain in the next section why we cache the C class in a weak global reference instead of a global reference.
Given a cached JavaVM interface pointer it is trivial to implement a utility function that allows the native code to obtain the JNIEnv interface pointer for the current thread (§8.1.4) :
JNIEnv *JNU_GetEnv()
{
JNIEnv *env;
(*cached_jvm)->GetEnv(cached_jvm,
(void **)&env,
JNI_VERSION_1_2);
return env;
}
8.4.2 The JNI_OnUnload Handler
Intuitively, the virtual machine calls the JNI_OnUnload handler when it unloads a JNI native library. This is not precise enough, however. When does the virtual machine determine that it can unload a native library? Which thread runs the JNI_OnUnload handler?
The rules of unloading native libraries are as follows:
The virtual machine associates each native library with the class loader L of the class C that issues the System.loadLibrary call.
The virtual machine calls the JNI_OnUnload handler and unloads the native library after it determines that the class loader L is no longer a live object. Because a class loader refers to all the classes it defines, this implies that C can be unloaded as well.
The JNI_OnUnload handler runs in a finalizer, and is either invoked synchroniously by java.lang.System.runFinalization or invoked asynchronously by the virtual machine.
Here is the definition of a JNI_OnUnload handler that cleans up the resources allocated by the JNI_OnLoad handler in the last section:
JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
if ((jvm)->GetEnv(jvm, (void *)&env, JNI_VERSION_1_2)) {
return;
}
(*env)->DeleteWeakGlobalRef(env, Class_C);
return;
}
The JNI_OnUnload function deletes the weak global reference to the C class created in the JNI_OnLoad handler. We need not delete the method ID MID_C_g because the virtual machine automatically reclaims the resources needed to represent C’s method IDs when unloading its defining class C.
We are now ready to explain why we cache the C class in a weak global reference instead of a global reference. A global reference would keep C alive, which in turn would keep C’s class loader alive. Given that the native library is associated with C’s class loader L, the native library would not be unloaded and JNI_OnUnload would not be called.
The JNI_OnUnload handler runs in a finalizer. In contrast, the JNI_OnLoad handler runs in the thread that initiates the System.loadLibrary call. Because JNI_OnUnload runs in an unknown thread context, to avoid possible deadlocks, you should avoid complex synchronization and locking operations in JNI_OnUnload. The JNI_OnUnload handler typically carries out simple tasks such as releasing the resources allocated by the native library.
The JNI_OnUnload handler runs when the class loader that loaded the library and all classes defined by that class loader are no longer alive. The JNI_OnUnload handler must not use these classes in any way. In the above JNI_OnUnload definition, you must not perform any operations that assume Class_C still refers to a valid class. The DeleteWeakGlobalRef call in the example frees the memory for the weak global reference itself, but does not manipulate the referred class C in any way.
In summary, you should be careful when writing JNI_OnUnload handlers. Avoid complex locking operations that may introduce deadlocks. Keep in mind that classes have been unloaded when the JNI_OnUnload handler is invoked.
8.5 Reflection Support
Reflection generally refers to manipulating language-level constructs at runtime. For example, reflection allows you to discover at run time the name of arbitrary class objects and the set of fields and methods defined in the class. Reflection support is provided at the Java programming language level through the java.lang.reflect package as well as some methods in the java.lang.Object and java.lang.Class classes. Although you can always call the corresponding Java API to carry out reflective operations, the JNI provides the following functions to make the frequent reflective operations from native code more efficient and convenient:
GetSuperclass returns the superclass of a given class reference.
IsAssignableFrom checks whether instances of one class can be used when instances of another class are expected.
GetObjectClass returns the class of a given jobject reference.
IsInstanceOf checks whether a jobject reference is an instance of a given class.
FromReflectedField and ToReflectedField allow the native code to convert between field IDs and java.lang.reflect.Field objects. They are new additions in Java 2 SDK release 1.2.
FromReflectedMethod and ToReflectedMethod allow the native code to convert between method IDs, java.lang.reflect.Method objects and java.lang.reflect.Constructor objects. They are new additions in Java 2 SDK release 1.2.
Ch24:测量Java应用程序的CPU和内存占用率
测量CPU和内存的占用率常常是检查Java应用程序是否达到特定性能的一个重要环节。尽管Java提 供了一些重要的方法用于测量其堆栈大小,但是使用标准的API是无法测量本机Java进程的大小和 CPU当前的使用率的。这种测量的结果对于开发人员来说非常重要,它会提供应用程序的实时性能和效率信息。不幸的是,这样的信息只能从操作系统直接获取, 而这已经超出了Java标准的可移植能力。
一个主要的解决方案是使用操作系统自带的本机系统调用,将数据通过JNI(Java Native Interface,Java本机接口)传输给Java.与调用各个平台专用的外部命令(比如ps)并分析输出结果不同,这种方案始终是一种很可靠的方 式。以前碰到这样的问题时,我尝试过使用Vladimir Roubtsov自己编写的一个很小的库,它只能在Win32系统下测量进程的CPU占用率。但是,这个库的能力十分有限,所以我需要某种方式能够同时在Windows和Solaris平台上测量CPU和内存的占用率。
我扩展了这个库的能力,在Windows和Solaris 8平台上实现了所有功能。新的库能够测量纯CPU使用时间、CPU使用的百分比、本机剩余内存和已经使用的内存、Java进程的本机内存大小、系统信息 (比如操作系统的名字、补丁程序、硬件信息等)。它由三部分实现: Java通用的部分、Windows实现,以及Solaris实现。依靠操作系统的部分用纯C语言实现。
编辑提示:本文可以下载,所有的源代码都以单独的文本方式列出来了。

所以,我们将创建一个简单的JNI库,用于同C层里的操作系统进行沟通,并把生成的数据提供给Java应用程序。首先,我们要创建一个 SystemInformation类(列表A),为测量和记录CPU的使用率和其他与系统相关的信息提供一个简单的API.这个类是抽象的,因为它公开 的是一个完全静态的API.

列表A

package com.vladium.utils; 
public abstract class SystemInformation
{
// public: ……………………………………………………….

/**
* A simple class to represent data snapshots taken by {@link #makeCPUUsageSnapshot}.
*/
public static final class CPUUsageSnapshot
{
public final long m_time, m_CPUTime;

// constructor is private to ensure that makeCPUUsageSnapshot()
// is used as the factory method for this class:
private CPUUsageSnapshot (final long time, final long CPUTime)
{
m_time = time;
m_CPUTime = CPUTime;
}

} // end of nested class
// Custom exception class for throwing
public static final class NegativeCPUTime extends Exception {
}
/**
* Minimum time difference [in milliseconds] enforced for the inputs into
* {@link #getProcessCPUUsage(SystemInformation.CPUUsageSnapshot,SystemInformation.CPUUsageSnapshot)}.
* The motivation for this restriction is the fact that System.currentTimeMillis()
* on some systems has a low resolution (e.g., 10ms on win32). The current value
* is 100 ms.
*/
public static final int MIN_ELAPSED_TIME = 100;

/**
* Creates a CPU usage data snapshot by associating CPU time used with system
* time. The resulting data can be fed into
* {@link #getProcessCPUUsage(SystemInformation.CPUUsageSnapshot,SystemInformation.CPUUsageSnapshot)}.
*/
public static CPUUsageSnapshot makeCPUUsageSnapshot() throws SystemInformation.NegativeCPUTime
{
long prCPUTime = getProcessCPUTime ();
if (prCPUTime<0) throw new NegativeCPUTime();
return new CPUUsageSnapshot (System.currentTimeMillis (), getProcessCPUTime ());
}

/**
* Computes CPU usage (fraction of 1.0) between start.m_CPUTime and
* end.m_CPUTime time points [1.0 corresponds to 100% utilization of
* all processors].
*
* @throws IllegalArgumentException if start and end time points are less than
* {@link #MIN_ELAPSED_TIME} ms apart.
* @throws IllegalArgumentException if either argument is null;
*/
public static double getProcessCPUUsage (final CPUUsageSnapshot start, final CPUUsageSnapshot end)
{
if (start == null) throw new IllegalArgumentException (“null input: start”);
if (end == null) throw new IllegalArgumentException (“null input: end”);
if (end.m_time < start.m_time + MIN_ELAPSED_TIME)
throw new IllegalArgumentException (“end time must be at least ” + MIN_ELAPSED_TIME + ” ms later than start time”);

return ((double)(end.m_CPUTime - start.m_CPUTime)) / (end.m_time - start.m_time);
}

/**
* Returns the PID of the current process. The result is useful when you need
* to integrate a Java app with external tools.
*/
public static native int getProcessID ();
/**
* Returns the number of processors on machine
*/
public static native int getCPUs ();
/**
* Returns CPU (kernel + user) time used by the current process [in milliseconds].
* The returned value is adjusted for the number of processors in the system.
*/
public static native long getProcessCPUTime ();
/**
* Returns CPU (kernel + user) time used by the current process [in perecents].
* The returned value is either CPU percentage, or zero if this is not supported by OS.
* Currently it is supported by Solaris8, and not supported by Windows XP
*/
public static native double getProcessCPUPercentage();
/**
* Returns maximum memory available in the system.
*/
public static native long getMaxMem ();
/**
* Returns current free memory in the system.
*/
public static native long getFreeMem ();
/**
* Returns system name info like “uname” command output
*/
public static native String getSysInfo ();
/**
* Returns CPU usage (fraction of 1.0) so far by the current process. This is a total
* for all processors since the process creation time.
*/
public static native double getProcessCPUUsage ();
/**
* Returns current space allocated for the process, in Kbytes. Those pages may or may not be in memory.
*/
public static native long getMemoryUsage();
/**
* Returns current process space being resident in memory, in Kbytes.
*/
public static native long getMemoryResident();
/**
* Sets the system native process PID for which all measurements will be done.
* If this method is not called then the current JVM pid will act as a default.
* Returns the native-dependent error code, or 0 in case of success.
*/
public static native int setPid(int pid);
/**
* Closes native-dependent process handle, if necessary.
*/
public static native int detachProcess();
// protected: …………………………………………………….
// package: ………………………………………………………
// private: ………………………………………………………

private SystemInformation () {} // prevent subclassing

private static final String SILIB = “silib”;

static
{
// loading a native lib in a static initializer ensures that it is
// available done before any method in this class is called:
try
{
System.loadLibrary (SILIB);
}
catch (UnsatisfiedLinkError e)
{
System.out.println (“native lib ‘” + SILIB + “’ not found in ‘java.library.path’: ” + System.getProperty (“java.library.path”));

throw e; // re-throw
}
}
} // end of class

最重要的方法是getProcessCPUTime(),它会返回当前进程的CPU时间(内核和用户)的毫秒数,或者是PID在初始化期间被传送给本机库 的进程所消耗的CPU时间。返回的值应该根据系统的处理器的个数进行调整;下面我们来看看这是如何在本机代码里做到的。我们用修改符native来声明这 个方法,这意味着它必须在JNI代码里实现。getProcessCPUTime()方法用来给CPU的使用率数据进行快照,方式是将经过测量的CPU时 间与当前的系统时间进行关联。这样的数据库快照在makeCPUUsageSnapshot()方法里进行,并输出一个CPUUsageSnapshot 容器对象。这样,测量CPU使用率的原理就很容易理解了:我们按照给定的时间间隔进行两次CPU快照,按1.0的分数来计算两个时间点之间的CPU使用 率,方式是两点所在的CPU时间差除以两点所在系统时间差。下面就是getProcessCPUUsage()方法的工作原理:
public static double getProcessCPUUsage (final CPUUsageSnapshot start, final CPUUsageSnapshot end)
{
if (start == null) throw new IllegalArgumentException (“null input: start”);
if (end == null) throw new IllegalArgumentException (“null input: end”);
if (end.m_time < start.m_time + MIN_ELAPSED_TIME)
throw new IllegalArgumentException (“end time must be at least ” + MIN_ELAPSED_TIME + ” ms later than start time”);

    return ((double)(end.m_CPUTime - start.m_CPUTime)) / (end.m_time - start.m_time);
}
只要我们知道分母里的快照之间的时间间隔,以及分子里进程花在活动状态上的CPU时间,我们就会得到所测量的时间间隔过程中进程的CPU使用率;1.0就代表所有处理器100%的使用率。
事实上这种方式可以用在所有版本的UNIX的ps工具和Windows的任务管理器上,这两个都是用于监视特定进程的CPU使用率的程序。很显然,时间间 隔越长,我们得到的结果就越平均、越不准确。但是最小时差应该被强制输入getProcessCPUUsage()。这种限制的原因是某些系统上的 System.currentTimeMillis()的解析度很低。Solaris 8操作系统提供了一个系统调用,用于从内核表里直接获得CPU使用率。出于这个目的,我们拥有的getProcessCPUPercentage()方法 会以百分比的形式返回进程所使用的CPU时间。如果这个特性不被操作系统支持(比如在Windows下),那么JNI库就会根据我们应用程序的设计返回一 个负值。
还有其他一些本机声明要在本机部分实现:
getCPUs()用来返回机器上处理器的个数
getMaxMem()用来返回系统上可用的最大物理内存
getFreeMem()用来返回系统上当前可用内存
getSysInfo()用来返回系统信息,包括一些硬件和操作系统的详细信息
getMemoryUsage()用来返回分配给进程的空间,以KB为单位(这些页面文件可能在内存里,也有可能不在内存里)
getMemoryResident()用来返回当前进程驻留在内存里的空间,以KB为单位。
所有这些方法对于不同的Java开发人员来说常常都是非常有用的。为了确保本机JNI库被调入内存并在调用任何本机方法之前被初始化,这个库被加载到一个静态初始值里:

static
{
try
{
System.loadLibrary (SILIB);
}
catch (UnsatisfiedLinkError e)
{
System.out.println (“native lib ‘” + SILIB + “’ not found in ‘Java.library.path’: ” + System.getProperty (“Java.library.path”));

        throw e; // re-throw
    }
}
在初始化一个.dll(或者.so)库之后,我们可以直接使用本机声明的方法:

final SystemInformation.CPUUsageSnapshot m_prevSnapshot =
SystemInformation.makeCPUUsageSnapshot ();
Thread.sleep(1000);
final SystemInformation.CPUUsageSnapshot event =
SystemInformation.makeCPUUsageSnapshot ();
final long memorySize = SystemInformation.getMemoryUsage();
final long residentSize = SystemInformation.getMemoryResident();
long freemem = SystemInformation.getFreeMem()/1024;
long maxmem = SystemInformation.getMaxMem()/1024;
double receivedCPUUsage = 100.0 * SystemInformation.getProcessCPUUsage (m_prevSnapshot, event);
System.out.println(“Current CPU usage is “+receivedCPUUsage+”%”);
现在让我们来分别看看针对Windows和Solaris的JNI本机实现。C头文件silib.h(列表B)能够用JDK里的Javah工具生成,或者手动编写。
列表B
/* DO NOT EDIT THIS FILE - it is machine generated */

include

/* Header for class com_vladium_utils_SystemInformation */

ifndef _Included_com_vladium_utils_SystemInformation

define _Included_com_vladium_utils_SystemInformation

ifdef __cplusplus

extern “C” {

endif

/*
* Class: com_vladium_utils_SystemInformation
* Method: getProcessID
* Signature: ()I
*/
JNIEXPORT jint
JNICALL Java_com_vladium_utils_SystemInformation_getProcessID (JNIEnv *, jclass);
/*
* Class: com_vladium_utils_SystemInformation
* Method: getCPUs
* Signature: ()I
*/
JNIEXPORT jint
JNICALL Java_com_vladium_utils_SystemInformation_getCPUs (JNIEnv *, jclass);
/*
* Class: com_vladium_utils_SystemInformation
* Method: getProcessCPUTime
* Signature: ()J
*/
JNIEXPORT jlong
JNICALL Java_com_vladium_utils_SystemInformation_getProcessCPUTime (JNIEnv *, jclass);
/*
* Class: com_vladium_utils_SystemInformation
* Method: getProcessCPUUsage
* Signature: ()D
*/
JNIEXPORT jdouble
JNICALL Java_com_vladium_utils_SystemInformation_getProcessCPUUsage (JNIEnv *, jclass);
/*
* Class: com_vladium_utils_SystemInformation
* Method: getPagefileUsage
* Signature: ()J
*/
JNIEXPORT jlong
JNICALL Java_com_vladium_utils_SystemInformation_getPagefileUsage (JNIEnv *, jclass);

ifdef __cplusplus

}

endif

endif

Windows
首先我们来看看Windows的实现(列表C)。
列表C
/* ————————————————————————- */
/*
* An implementation of JNI methods in com.vladium.utils.SystemInformation
* class. The author compiled it using Microsoft Visual C++ and GCC for Win32 but the code
* should be easy to use with any compiler for win32 platform.
*
* For simplicity, this implementaion assumes JNI 1.2+ and omits error handling.
*
* Enhanced by Peter V. Mikhalenko (C) 2004, Deutsche Bank [peter@mikhalenko.com]
* Original source (C) 2002, Vladimir Roubtsov [vlad@trilogy.com]
*/
/* ————————————————————————- */

include

include

include

include

include

include

include “com_vladium_utils_SystemInformation.h”

static jint s_PID;
static HANDLE s_currentProcess;
static int alreadyDetached;
static int s_numberOfProcessors;
static SYSTEM_INFO systemInfo;
static WORD processorArchitecture;
static DWORD pageSize;
static DWORD processorType;
static WORD processorLevel;
static WORD processorRevision;

define INFO_BUFFER_SIZE 32768

define BUFSIZE 2048

/* ————————————————————————- */
/*
* A helper function for converting FILETIME to a LONGLONG [safe from memory
* alignment point of view].
*/
static LONGLONG
fileTimeToInt64 (const FILETIME * time)
{
ULARGE_INTEGER _time;
_time.LowPart = time->dwLowDateTime;
_time.HighPart = time->dwHighDateTime;
return _time.QuadPart;
}
/* ………………………………………………………………. */
/*
* This method was added in JNI 1.2. It is executed once before any other
* methods are called and is ostensibly for negotiating JNI spec versions, but
* can also be conveniently used for initializing variables that will not
* change throughout the lifetime of this process.
*/
JNIEXPORT jint JNICALL
JNI_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;
return JNI_VERSION_1_2;
}
/* ………………………………………………………………. */
JNIEXPORT void JNICALL
JNI_OnUnload (JavaVM * vm, void * reserved)
{
if (!alreadyDetached && s_currentProcess!=NULL) {
CloseHandle(s_currentProcess);
printf(“[JNI Unload] Detached from native process.
“);
fflush(stdout);
}
}
/* ………………………………………………………………. */
/*
* Class: com_vladium_utils_SystemInformation
* Method: getCPUs
* Signature: ()I
*/
JNIEXPORT jint JNICALL
Java_com_vladium_utils_SystemInformation_getCPUs (JNIEnv * env, jclass cls)
{
return (jint)s_numberOfProcessors;
}
/* ………………………………………………………………. */
/*
* Class: com_vladium_utils_SystemInformation
* Method: getSysInfo
* Signature: ()S
*/
JNIEXPORT jstring JNICALL
Java_com_vladium_utils_SystemInformation_getSysInfo (JNIEnv * env, jclass cls)
{
char buf[2048];
char buf2[512];
*buf=0;
OSVERSIONINFOEX osvi;
BOOL bOsVersionInfoEx;
TCHAR infoBuf[INFO_BUFFER_SIZE];
DWORD bufCharCount = INFO_BUFFER_SIZE;

// Try calling GetVersionEx using the OSVERSIONINFOEX structure.
// If that fails, try using the OSVERSIONINFO structure.
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
if( !(bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi)) )
{
   osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
   if (! GetVersionEx ( (OSVERSIONINFO *) &osvi) ) {
 // Return empty string in case of problems

goto next_label;
}
}
switch (osvi.dwPlatformId)
{
// Test for the Windows NT product family.
case VER_PLATFORM_WIN32_NT:
// Test for the specific product.
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 “);
// Test for specific product on Windows NT 4.0 SP6 and later.
if( bOsVersionInfoEx )
{
// Test for the workstation type.
if ( osvi.wProductType == VER_NT_WORKSTATION )
{
if( osvi.dwMajorVersion == 4 )
strcat(buf,”Workstation 4.0 ” );
else if( osvi.wSuiteMask & VER_SUITE_PERSONAL )
strcat(buf,”Home Edition ” );
else
strcat(buf,”Professional ” );
}
// Test for the server type.
else if ( 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,”Datacenter Edition ” );
else if( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
strcat(buf,”Enterprise Edition ” );
else if ( osvi.wSuiteMask == VER_SUITE_BLADE )
strcat(buf,”Web Edition ” );
else
strcat(buf,”Standard Edition ” );
}
else if( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0 )
{
if( osvi.wSuiteMask & VER_SUITE_DATACENTER )
strcat(buf,”Datacenter Server ” );
else if( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
strcat(buf,”Advanced Server ” );
else
strcat(buf,”Server ” );
}
else // Windows NT 4.0
{
if( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
strcat(buf,”Server 4.0, Enterprise Edition ” );
else
strcat(buf,”Server 4.0 ” );
}
}
}
else // Test for specific product on Windows NT 4.0 SP5 and earlier
{
HKEY hKey;
char szProductType[BUFSIZE];
DWORD dwBufLen=BUFSIZE;
LONG lRet;
lRet = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
“SYSTEMCurrentControlSetControlProductOptions”,
0, KEY_QUERY_VALUE, &hKey );
if( lRet != ERROR_SUCCESS ) {
goto next_label;
}
lRet = RegQueryValueEx( hKey, “ProductType”, NULL, NULL,
(LPBYTE) szProductType, &dwBufLen);
if( (lRet != ERROR_SUCCESS) || (dwBufLen > BUFSIZE) ) {
goto next_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,”Advanced Server ” );
sprintf(buf2, “%d.%d “, (int)osvi.dwMajorVersion, (int)osvi.dwMinorVersion );
strcat(buf,buf2);
}
// Display service pack (if any) and build number.
if( osvi.dwMajorVersion == 4 &&
lstrcmpi( osvi.szCSDVersion, “Service Pack 6” ) == 0 )
{
HKEY hKey;
LONG lRet;
// Test for SP6 versus SP6a.
lRet = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
“SOFTWAREMicrosoftWindows NTCurrentVersionHotfixQ246009”,
0, KEY_QUERY_VALUE, &hKey );
if( lRet == ERROR_SUCCESS ) {
sprintf(buf2, “SP 6a (Build %d), “, (int)(osvi.dwBuildNumber & 0xFFFF) );
strcat(buf,buf2);
}
else // Windows NT 4.0 prior to SP6a
{
sprintf(buf2, “%s (Build %d), “,
osvi.szCSDVersion,
(int)(osvi.dwBuildNumber & 0xFFFF));
strcat(buf,buf2);
}
RegCloseKey( hKey );
}
else // not Windows NT 4.0
{
sprintf(buf2, “%s (Build %d), “,
osvi.szCSDVersion,
(int)(osvi.dwBuildNumber & 0xFFFF));
strcat(buf,buf2);
}

     break;
  // Test for the Windows Me/98/95.
  case VER_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;
  case VER_PLATFORM_WIN32s:
     strcat(buf,"Win32s ");
     break;

}
next_label:
strcat(buf,”
on “);
// Get and display the name of the computer.
bufCharCount = INFO_BUFFER_SIZE;
if( !GetComputerName( infoBuf, &bufCharCount ) )
goto next_label_2;
strcat(buf, infoBuf );
next_label_2:
strcat(buf,” (“);
if (!(osvi.dwPlatformId==VER_PLATFORM_WIN32_WINDOWS && osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 0)) {
// Win95 does not keep CPU info in registry
LONG lRet;
HKEY hKey;
char szOrigCPUType[BUFSIZE];
int i=0;
DWORD dwBufLen=BUFSIZE;
lRet = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
“HARDWAREDESCRIPTIONSystemCentralProcessor”,
0, KEY_QUERY_VALUE, &hKey );
if( lRet != ERROR_SUCCESS ) {
goto next_label_3;
}
lRet = RegQueryValueEx( hKey, “ProcessorNameString”, NULL, NULL,
(LPBYTE) szOrigCPUType, &dwBufLen);
if( (lRet != ERROR_SUCCESS) || (dwBufLen > BUFSIZE) ) {
goto next_label_3;
}
RegCloseKey( hKey );
if (strlen(szOrigCPUType)>0) {
while(szOrigCPUType[i]==’ ’ && szOrigCPUType[i]!=0) i++;
strcat(buf,szOrigCPUType+i);
} else goto next_label_3;
} else {
next_label_3:
if (processorArchitecture==PROCESSOR_ARCHITECTURE_UNKNOWN) strcat(buf,”unknown_arch”);
else if (processorArchitecture==PROCESSOR_ARCHITECTURE_INTEL) {
strcat(buf,”Intel “);
sprintf(buf2,”level %d “,processorLevel);
strcat(buf,buf2);
} else if (processorArchitecture==PROCESSOR_ARCHITECTURE_IA64) strcat(buf,”IA64 “);
else if (processorArchitecture==PROCESSOR_ARCHITECTURE_MIPS) strcat(buf,”MIPS “);
else if (processorArchitecture==PROCESSOR_ARCHITECTURE_ALPHA) strcat(buf,”Alpha “);
else if (processorArchitecture==PROCESSOR_ARCHITECTURE_PPC) strcat(buf,”PowerPC “);
else if (processorArchitecture==PROCESSOR_ARCHITECTURE_SHX) strcat(buf,”SHX “);
else if (processorArchitecture==PROCESSOR_ARCHITECTURE_ALPHA64) strcat(buf,”Alpha64 “);
else strcat(buf,”unknown_arch “);
}
strcat(buf,”)”);
jstring retval = (*env)->NewStringUTF(env,buf);
return retval;
}
/* ………………………………………………………………. */

/*
* Class: com_vladium_utils_SystemInformation
* Method: getProcessID
* Signature: ()I
*/
JNIEXPORT jint JNICALL
Java_com_vladium_utils_SystemInformation_getProcessID (JNIEnv * env, jclass cls)
{
return s_PID;
}
/* ………………………………………………………………. */
/*
* Class: com_vladium_utils_SystemInformation
* Method: setPid
* Signature: ()I
*/
JNIEXPORT jint JNICALL
Java_com_vladium_utils_SystemInformation_setPid (JNIEnv * env, jclass cls, jint pid)
{
DWORD errCode;
LPVOID lpMsgBuf;
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] Could not attach to native process.
Error code: %ld
Error description: %s
“,errCode,lpMsgBuf);
fflush(stdout);
LocalFree(lpMsgBuf);
return errCode;
}
printf(“[CPUmon] Attached to native process.
“);
fflush(stdout);
return 0;
}
/* ………………………………………………………………. */
/*
* Class: com_vladium_utils_SystemInformation
* Method: detachProcess
* Signature: ()I
*/
JNIEXPORT jint JNICALL
Java_com_vladium_utils_SystemInformation_detachProcess (JNIEnv * env, jclass cls)
{
if (!alreadyDetached && s_currentProcess!=NULL) {
CloseHandle(s_currentProcess);
alreadyDetached = 1;
printf(“[CPUmon] Detached from native process.
“);
fflush(stdout);
}
return 0;
}
/* ………………………………………………………………. */
/*
* Class: com_vladium_utils_SystemInformation
* Method: getProcessCPUTime
* Signature: ()J
*/
JNIEXPORT jlong JNICALL
Java_com_vladium_utils_SystemInformation_getProcessCPUTime (JNIEnv * env, jclass cls)
{
FILETIME creationTime, exitTime, kernelTime, userTime;
DWORD errCode;
LPVOID lpMsgBuf;
BOOL resultSuccessful = 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] An error occured while trying to get CPU time.
Error code: %ld
Error description: %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
*/
JNIEXPORT jlong JNICALL
Java_com_vladium_utils_SystemInformation_getMaxMem (JNIEnv * env, jclass cls)
{
MEMORYSTATUS stat;
GlobalMemoryStatus (&stat);
return (jlong)(stat.dwTotalPhys/1024);
}
/* ………………………………………………………………. */
/*
* Class: com_vladium_utils_SystemInformation
* Method: getFreeMem
* Signature: ()J
*/
JNIEXPORT jlong JNICALL
Java_com_vladium_utils_SystemInformation_getFreeMem (JNIEnv * env, jclass cls)
{
MEMORYSTATUS stat;
GlobalMemoryStatus (&stat);
return (jlong)(stat.dwAvailPhys/1024);
}
/* ………………………………………………………………. */

/* define min elapsed time (in units of 10E-7 sec): */

define MIN_ELAPSED_TIME (10000)

/*
* Class: com_vladium_utils_SystemInformation
* Method: getProcessCPUUsage
* Signature: ()D
*/
JNIEXPORT jdouble JNICALL
Java_com_vladium_utils_SystemInformation_getProcessCPUUsage (JNIEnv * env, jclass cls)
{
FILETIME creationTime, exitTime, kernelTime, userTime, nowTime;
LONGLONG elapsedTime;
DWORD errCode;
LPVOID lpMsgBuf;
BOOL resultSuccessful = 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] An error occured while trying to get CPU time.
Error code: %ld
Error description: %s
“,errCode,lpMsgBuf);
fflush(stdout);
LocalFree(lpMsgBuf);
return -1.0;
}
GetSystemTimeAsFileTime (& nowTime);
/*
NOTE: win32 system time is not very precise [~10ms resolution], use
sufficiently long sampling intervals if you make use of this method.
*/
elapsedTime = fileTimeToInt64 (& nowTime) - fileTimeToInt64 (& creationTime);
if (elapsedTime < MIN_ELAPSED_TIME)
return 0.0;
else
return ((jdouble) (fileTimeToInt64 (& kernelTime) + fileTimeToInt64 (& userTime))) /
(s_numberOfProcessors * elapsedTime);
}
/* ………………………………………………………………. */
/*
* Class: com_vladium_utils_SystemInformation
* Method: getProcessCPUPercentage
* Signature: ()D
*/
JNIEXPORT jdouble JNICALL
Java_com_vladium_utils_SystemInformation_getProcessCPUPercentage (JNIEnv * env, jclass cls)
{
// Not implemented on Windows
return (jdouble)(-1.0);
}
/* ………………………………………………………………. */
/*
* Class: com_vladium_utils_SystemInformation
* Method: getMemoryUsage
* Signature: ()J
*/
JNIEXPORT jlong JNICALL
Java_com_vladium_utils_SystemInformation_getMemoryUsage (JNIEnv * env, jclass cls)
{
PROCESS_MEMORY_COUNTERS pmc;
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
*/
JNIEXPORT jlong JNICALL
Java_com_vladium_utils_SystemInformation_getMemoryResident (JNIEnv * env, jclass cls)
{
PROCESS_MEMORY_COUNTERS pmc;
if ( GetProcessMemoryInfo( s_currentProcess, &pmc, sizeof(pmc)) )
{
return (jlong)(pmc.WorkingSetSize/1024);
} else {
return (jlong)(0);
}
}

undef MIN_ELAPSED_TIME

/* ————————————————————————- */
/* end of file */

JNI里有两个特殊的函数——JNI_OnLoad和JNI_OnUnload,它们分别在加载和卸载库的时候被调用。JNI_OnLoad在调用其他任 何方法之前被执行,而且能够很方便地用于初始化在这一进程的生命周期中没有发生变化的变量,并用于协调JNI规范的版本。在默认情况下,库会测量它自己的 进程的参数,但是通过调用systemInformation.setPid()方法它可以从Java应用程序被重载。s_PID C变量用来保存PID,而s_currentProcess用来保存进程句柄(用于Windows的是HANDLE类型,而用于Solaris的是int 类型)。为了读取的一些参数,应该首先打开进程句柄,我们需要在库关闭使用的时候停止同一个进程句柄(通常它在JVM因为相同的原因而关闭的时候发生)。 这就是JNI_OnUnload()函数起作用的地方。但是,JVM的一些实现事实上没有调用JNI_OnUnload(),还有发生句柄会永远打开的危 险。为了降低这种可能性,我们应该在Java应用程序里加入一个明确调用detachProcess() C函数的关闭挂钩。下面就是我们加入关闭挂钩的方法:

if (pid!=-1) {
int result = SystemInformation.setPid(pid);
if (result!=0) {
return;
}
hasPid = true;
// Create shutdown hook for proper process detach
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
SystemInformation.detachProcess();
}
});
}
通过调用WinAPI里的GetSystemInfo(),我们还可以获得关于中央处理器的一些信息。只要它是CPU的占用率根据这个值来调节,测量进程 最重要的值就是处理器的个数(s_numberOfProcessors)。SystemInformation.getSysInfo()的Win32 实现相当麻烦,因为在各个版本的Windows里,关于操作系统的版本、补丁、服务包以及相关硬件等信息被以不同的方式保存。所以需要读者来分析相关的源 代码和代码中的注释。下面就是Windows XP上代码输出的示例:
System.out.println(“SysInfo: ”+SystemInformation.getSysInfo()):

SysInfo: WinXP Professional Service Pack 1 (Build 2600),
on DBMOSWS2132 (Intel(R) Xeon(TM) CPU 1.70GHz)
And the same code on Solaris will give:
SysInfo: SunOS 5.8 sxav-dev Generic_108528-29 sun4u sparc
SUNW,Ultra-Enterprise Sun_Microsystems
为了获得CPU上进程所占用的总时间,我们使用了WinAPI的函数GetProcessTimes.其他的函数实现都非常简单,直接调用WinAPI, 所以没有什么必要讨论它们。列表D里是用于Windows版本的GCC的make.bat文件,用来帮助读者创建相关的。dll库。
列表D
gcc -D_JNI_IMPLEMENTATION_ -Wl,——kill-at -IC:/jdk1.3.1_12/include -IC:/jdk1.3.1_12/include/win32 -shared C:/cpu_profile/src/native/com_vladium_utils_SystemInformation.c -o C:/cpu_profile/dll/silib.dll C:/MinGW/lib/libpsapi.a 这个库的Solaris实现见列表E和列表F.这两个都是C语言文件,应该被编译到一个共享库(.so)里。用于编译共享库的帮助器make.sh见列表 G.所有基于Solaris系统的调用被移到列表F里,这使得列表E就是一个JNI的简单包装程序。Solaris实现要比Win32实现更加复杂,要求 更多的临时数据结构、内核和进程表。列出的代码里有更多的注释。

列表E
/* ————————————————————————- */
/*
* An implementation of JNI methods in com.vladium.utils.SystemInformation
* class.
* This is a ported version from Win32 to Solaris.
*
* For simplicity, this implementaion assumes JNI 1.2+ and omits error handling.
*
* Port from Win32 by Peter V. Mikhalenko (C) 2004, Deutsche Bank [peter@mikhalenko.ru]
* Original source (C) 2002, Vladimir Roubtsov [vlad@trilogy.com]
*/
/* ————————————————————————- */

include

include “com_vladium_utils_SystemInformation.h”

// Helper Solaris8-dependent external routines
extern int sol_getCPUs();
extern int sol_getFreeMem();
extern int sol_getMaxMem();
extern long int sol_getProcessCPUTime(int pid,int nproc);
extern double sol_getProcessCPUPercentage(int pid);
extern long sol_getMemoryUsage(int pid);
extern long sol_getMemoryResident(int pid);
extern char* sol_getSysInfo();
extern void initKVM();
static jint s_PID;
static int s_numberOfProcessors;
static int externalCPUmon;
static int alreadyDetached;
/* ————————————————————————- */
/* ………………………………………………………………. */
/*
* This method was added in JNI 1.2. It is executed once before any other
* methods are called and is ostensibly for negotiating JNI spec versions, but
* can also be conveniently used for initializing variables that will not
* change throughout the lifetime of this process.
*/
JNIEXPORT jint JNICALL
JNI_OnLoad (JavaVM * vm, void * reserved)
{
s_PID = _getpid ();
externalCPUmon = 0;
alreadyDetached = 0;
/* use kstat to update all processor information */
s_numberOfProcessors = sol_getCPUs();
initKVM();
return JNI_VERSION_1_2;
}
/* ………………………………………………………………. */
/*
* Class: com_vladium_utils_SystemInformation
* Method: getCPUs
* Signature: ()I
*/
JNIEXPORT jint JNICALL
Java_com_vladium_utils_SystemInformation_getCPUs (JNIEnv * env, jclass cls)
{
return (jint)s_numberOfProcessors;
}
/* ………………………………………………………………. */
/*
* Class: com_vladium_utils_SystemInformation
* Method: getSysInfo
* Signature: ()S
*/
JNIEXPORT jstring JNICALL
Java_com_vladium_utils_SystemInformation_getSysInfo (JNIEnv * env, jclass cls)
{
char * buf = sol_getSysInfo();
jstring retval = (*env)->NewStringUTF(env,buf);
free(buf);
return retval;
}
/* ………………………………………………………………. */

/*
* Class: com_vladium_utils_SystemInformation
* Method: getProcessID
* Signature: ()I
*/
JNIEXPORT jint JNICALL
Java_com_vladium_utils_SystemInformation_getProcessID (JNIEnv * env, jclass cls)
{
return s_PID;
}
/* ………………………………………………………………. */
/*
* Class: com_vladium_utils_SystemInformation
* Method: setPid
* Signature: ()I
*/
JNIEXPORT jint JNICALL
Java_com_vladium_utils_SystemInformation_setPid (JNIEnv * env, jclass cls, jint pid)
{
s_PID = pid;
externalCPUmon = 1;
printf(“[CPUmon] Attached to process.
“);
fflush(stdout);
return 0;
}
/* ………………………………………………………………. */
/*
* Class: com_vladium_utils_SystemInformation
* Method: detachProcess
* Signature: ()I
*/
JNIEXPORT jint JNICALL
Java_com_vladium_utils_SystemInformation_detachProcess (JNIEnv * env, jclass cls)
{
if (externalCPUmon && !alreadyDetached) {
alreadyDetached = 1;
printf(“[CPUmon] Detached from process. “);
fflush(stdout);
}
return 0;
}
/* ………………………………………………………………. */
/*
* Class: com_vladium_utils_SystemInformation
* Method: getProcessCPUTime
* Signature: ()J
*/
JNIEXPORT jlong JNICALL
Java_com_vladium_utils_SystemInformation_getProcessCPUTime (JNIEnv * env, jclass cls)
{
return (jlong)sol_getProcessCPUTime((int)s_PID,s_numberOfProcessors);
}
/* ………………………………………………………………. */
/*
* Class: com_vladium_utils_SystemInformation
* Method: getMaxMem
* Signature: ()J
*/
JNIEXPORT jlong JNICALL
Java_com_vladium_utils_SystemInformation_getMaxMem (JNIEnv * env, jclass cls)
{
return (jlong)sol_getMaxMem();
}
/* ………………………………………………………………. */
/*
* Class: com_vladium_utils_SystemInformation
* Method: getFreeMem
* Signature: ()J
*/
JNIEXPORT jlong JNICALL
Java_com_vladium_utils_SystemInformation_getFreeMem (JNIEnv * env, jclass cls)
{
return (jlong)sol_getFreeMem();
}
/* ………………………………………………………………. */

/* define min elapsed time (in units of 10E-7 sec): */

define MIN_ELAPSED_TIME (10000)

/*
* Class: com_vladium_utils_SystemInformation
* Method: getProcessCPUUsage
* Signature: ()D
*/
JNIEXPORT jdouble JNICALL
Java_com_vladium_utils_SystemInformation_getProcessCPUUsage (JNIEnv * env, jclass cls)
{
return 0.0;
}
/* ………………………………………………………………. */
/*
* Class: com_vladium_utils_SystemInformation
* Method: getProcessCPUPercentage
* Signature: ()D
*/
JNIEXPORT jdouble JNICALL
Java_com_vladium_utils_SystemInformation_getProcessCPUPercentage (JNIEnv * env, jclass cls)
{
return (jdouble)sol_getProcessCPUPercentage((int)s_PID);
}
/* ………………………………………………………………. */

/*
* Class: com_vladium_utils_SystemInformation
* Method: getMemoryUsage
* Signature: ()J
*/
JNIEXPORT jlong JNICALL
Java_com_vladium_utils_SystemInformation_getMemoryUsage (JNIEnv * env, jclass cls)
{
return (jlong)sol_getMemoryUsage((int)s_PID);
}
/* ………………………………………………………………. */
/*
* Class: com_vladium_utils_SystemInformation
* Method: getMemoryResident
* Signature: ()J
*/
JNIEXPORT jlong JNICALL
Java_com_vladium_utils_SystemInformation_getMemoryResident (JNIEnv * env, jclass cls)
{
return (jlong)sol_getMemoryResident((int)s_PID);
}

undef MIN_ELAPSED_TIME

/* ————————————————————————- */
/* end of file */
列表F/* ————————————————————————- */
/*
* Solaris-dependent system routines and kernel calls
* Used for getting memory and CPU consumption statistics
*
* Author: Peter V. Mikhalenko (C) 2004, Deutsche Bank [peter@mikhalenko.ru]
*/
/* ————————————————————————- */

include

ifndef KSTAT_DATA_UINT32

define ui32 ul

endif

include

include

include

include

include

include

include

include

define _STRUCTURED_PROC 1

include

define prpsinfo psinfo

define pr_fill pr_nlwp

/* These require an ANSI C compiler “Reisser cpp” doesn’t like this */

define pr_state pr_lwp.pr_state

define pr_oldpri pr_lwp.pr_oldpri

define pr_nice pr_lwp.pr_nice

define pr_pri pr_lwp.pr_pri

define pr_onpro pr_lwp.pr_onpro

define ZOMBIE(p) ((p)->pr_nlwp == 0)

define SIZE_K(p) ((p)->pr_size)

define RSS_K(p) ((p)->pr_rssize)

define PROCFS “/proc”

/* definitions for indices in the nlist array */

define X_V 0

define X_MPID 1

define X_ANONINFO 2

define X_MAXMEM 3

define X_SWAPFS_MINFREE 4

define X_FREEMEM 5

define X_AVAILRMEM 6

define X_AVENRUN 7

define X_CPU 8

define X_NPROC 9

define X_NCPUS 10

static struct nlist nlst[] =
{
, /* 0 / / replaced by dynamic allocation */
, /* 1 */

if OSREV >= 56

/* this structure really has some extra fields, but the first three match */
, /* 2 */

else

, /* 2 */

endif

, /* 3 / / use sysconf */
, /* 4 / / used only w/ USE_ANONINFO */
, /* 5 / / available from kstat >= 2.5 */
, /* 6 / / available from kstat >= 2.5 */
, /* 7 / / available from kstat */
, /* 8 / / available from kstat */
, /* 9 / / available from kstat */
, /* 10 / / available from kstat */
};
static kstat_ctl_t *kc = NULL;
static kstat_t **cpu_ks;
static cpu_stat_t *cpu_stat;
static int ncpus;
kvm_t *kd;
static unsigned long freemem_offset;
static unsigned long maxmem_offset;
static unsigned long freemem = -1L;
static unsigned long maxmem = -1L;
/* pagetok function is really a pointer to an appropriate function */
static int pageshift;
static int (*p_pagetok) ();

define pagetok(size) ((*p_pagetok)(size))

int pagetok_none(int size) {
return(size);
}
int pagetok_left(int size) {
return(size << pageshift);
}
int pagetok_right(int size) {
return(size >> pageshift);
}

define UPDKCID(nk,ok)

if (nk == -1) {
perror(“kstat_read “);
exit(1);
}
if (nk != ok)
goto kcid_changed;
void initKVM() {
int i;
/* perform the kvm_open - suppress error here */
kd = kvm_open (NULL, NULL, NULL, O_RDONLY, NULL);
/* calculate pageshift value */
i = sysconf(_SC_PAGESIZE);
pageshift = 0;
while ((i >>= 1) > 0)
{
pageshift++;
}
/* calculate an amount to shift to K values */
/* remember that log base 2 of 1024 is 10 (i.e.: 2^10 = 1024) */
pageshift -= 10;
/* now determine which pageshift function is appropriate for the
result (have to because x << y is undefined for y < 0) */
if (pageshift > 0)
{
/* this is the most likely */
p_pagetok = pagetok_left;
}
else if (pageshift == 0)
{
p_pagetok = pagetok_none;
}
else
{
p_pagetok = pagetok_right;
pageshift = -pageshift;
}
}

define SI_LEN 512

define BUFSIZE 256

char * sol_getSysInfo() {
char * retbuf = (char*)malloc(SI_LEN);
int curend = 0;
int maxl = SI_LEN;
*retbuf=0;
char * buf = (char*)malloc(BUFSIZE);
long res = 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);
/* if kcid changed, pointer might be invalid */
UPDKCID(nkcid, kcid);
cpu_ks[ncpu] = ks;
ncpu++;
if (ncpu > ncpus)
{
fprintf(stderr, “kstat finds too many cpus: should be %d
“,
ncpus);
exit(1);
}
}
}
/* note that ncpu could be less than ncpus, but that’s okay */
changed = 0;
}
/* return the number of cpus found */
ncpus=ncpu;
return ncpu;
}
unsigned long sol_getMaxMem() {
maxmem = pagetok(sysconf(_SC_PHYS_PAGES));
return maxmem;
}
unsigned long sol_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) { /* always get freemem from kvm if we can*/
(void) getkval (freemem_offset, (int *) (&freemem), sizeof (freemem), “freemem”);
} else {
kn = kstat_data_lookup(ks, “freemem”);
if (kn)
freemem = kn->value.ul;
}
return (unsigned long)pagetok(freemem);
}
// Returns the number of milliseconds (not nanoseconds and seconds) elapsed on processor
// since process start. The returned value is adjusted for the number of processors in the system.
long int sol_getProcessCPUTime(int pid,int nproc) {
struct prpsinfo currproc;
int fd;
char buf[30];
long int retval=0;
snprintf(buf, sizeof(buf), “%s/%d/psinfo”, PROCFS, pid);
if ((fd = open (buf, O_RDONLY)) < 0) {
return 0L;
}
if (read(fd, &currproc, sizeof(psinfo_t)) != sizeof(psinfo_t)) {
(void)close(fd);
return 0L;
}
(void)close(fd);
retval = (currproc.pr_time.tv_sec * 1000 + currproc.pr_time.tv_nsec / 1000000) / nproc;
return retval;
}
// Returns percentage CPU by pid
// In Solaris 8 it is contained in procfs
double sol_getProcessCPUPercentage(int pid) {
struct prpsinfo currproc;
int fd;
char buf[30];
double retval=0.0;
snprintf(buf, sizeof(buf), “%s/%d/psinfo”, PROCFS, pid);
if ((fd = open (buf, O_RDONLY)) < 0) {
return 0;
}
if (read(fd, &currproc, sizeof(psinfo_t)) != sizeof(psinfo_t)) {
(void)close(fd);
return 0;
}
(void)close(fd);
retval = ((double)currproc.pr_pctcpu)/0x8000*100;
return retval;
}
// Returns current space allocated for the process, in bytes. Those pages may or may not be in memory.
long sol_getMemoryUsage(int pid) {
struct prpsinfo currproc;
int fd;
char buf[30];
double retval=0.0;
snprintf(buf, sizeof(buf), “%s/%d/psinfo”, PROCFS, pid);
if ((fd = open (buf, O_RDONLY)) < 0) {
return 0;
}
if (read(fd, &currproc, sizeof(psinfo_t)) != sizeof(psinfo_t)) {
(void)close(fd);
return 0;
}
(void)close(fd);
retval = currproc.pr_size;
return retval;
}
// Returns current process space being resident in memory.
long sol_getMemoryResident(int pid) {
struct prpsinfo currproc;
int fd;
char buf[30];
double retval=0.0;
snprintf(buf, sizeof(buf), “%s/%d/psinfo”, PROCFS, pid);
if ((fd = open (buf, O_RDONLY)) < 0) {
return 0;
}
if (read(fd, &currproc, sizeof(psinfo_t)) != sizeof(psinfo_t)) {
(void)close(fd);
return 0;
}
(void)close(fd);
retval = currproc.pr_rssize;
return retval;
}
/*
* getkval(offset, ptr, size, refstr) - get a value out of the kernel.
* “offset” is the byte offset into the kernel for the desired value,
* “ptr” points to a buffer into which the value is retrieved,
* “size” is the size of the buffer (and the object to retrieve),
* “refstr” is a reference string used when printing error meessages,
* if “refstr” starts with a ‘!’, then a failure on read will not
* be fatal (this may seem like a silly way to do things, but I
* really didn’t want the overhead of another argument).
*
*/
int
getkval (unsigned long offset,
int *ptr,
int size,
char *refstr)
{
if (kvm_read (kd, offset, (char *) ptr, size) != size)
{
if (*refstr == ‘!’)
{
return (0);
}
else
{
fprintf (stderr, “top: kvm_read for %s: %s
“, refstr, strerror(errno));
exit(23);
}
}
return (1);
}
列表G

!/bin/sh

gcc -G -lkvm -lkstat com_vladium_utils_SystemInformation.c -o libsilib.so solaris-extern.c

com_vladium_utils_SystemInformation.c is in Listing-E

solaris-extern.c is in Listing-F

在本文里,我已告诉你如何编程测量Java进程的内存和CPU占用率。当然用户可以通过查看Windows任务管理器或者ps的输出来达到相同的目的,但是重要的一点是,用户现在能够进行任何长期运行和/或自动的软件性能测试,这对于开发一个分布式或者可伸缩的,或者实时的性能关键的应用程序十分重要。加入获取系统软件和硬件信息的能力对于开发人员同样十分有用。这个库可以在Win32和Solaris平台上实现。但是,把它移植到其他UNIX平台上应该也不是问题,比如Linux.

Ch25:C++调用JAVA方法详解

本文主要参考http://tech.ccidnet.com/art/1081/20050413/237901_1.html 上的文章。
?

C++调用JAVA主要用到了SUN公司的JNI技术, JNI是Java Native Interface的 缩写。从Java 1.1开始,Java Native Interface (JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。相关资料见http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/jniTOC.html

?
开发环境安装及配置
?
1.1? 安装JDK

??????? 到SUN公司网站可以下载到最新版的JDK。下载下来后开始安装,一路选择默认配置即可,本文档中假定安装的是JDK1.4,安装目录为C:\j2sdk1.4.2_15。

?

1.2? 配置VC6.0

???????? 通过Visual C++ 6的菜单Tools→Options打开选项对话框。在Directories标签页下添加JDK的相关目录到Include和目录下。
????????????

?

?

?开发测试用到的JAVA类

2.1? 开发JAVA类
??????? 在硬盘的任意地方新建一个名叫test的文件夹,本文档示例中将test文件夹建立在C盘根目录,然后在里面新建一个名称叫Demo.java的JAVA文件,将下面测试用的代码粘贴到该文件中。
?
?
package test;
/**
* 该类是为了演示JNI如何访问各种对象属性等
*/
public class Demo
{
//用于演示如何访问静态的基本类型属性
public static int COUNT = 8;
//演示对象型属性
private String msg;
private int[] counts;

public Demo() 
{
    this("缺省构造函数");
}
/**
 * 演示如何访问构造器
 */
public Demo(String msg) 
{
    this.msg = msg;
    this.counts = null;
}
public String getMessage()
{
    return msg;
}
/**
 * 该方法演示如何访问一个静态方法
 */
public static String getHelloWorld()
{
    return "Hello world!";
}

/**
 * 该方法演示参数的传入传出及中文字符的处理
 */
public String append(String str, int i)
{
    return str + i;
}
/**
 * 演示数组对象的访问
 */
public int[] getCounts()
{
 return counts;
}
/**
 * 演示如何构造一个数组对象
*/
public void setCounts(int[] counts)
{
 this.counts = counts;
}
/**
 * 演示异常的捕捉
*/
public void throwExcp()throws IllegalAccessException
{
    throw new IllegalAccessException("exception occur.");
}

}
?
2.2 编译JAVA类
????? 运行CMD控制台程序进入命令行模式,输入命令javac -classpath c:\ c:\test\Demo.java,-classpath参数指定classpath的路径,这里就是test目录所在的路径。(注意:如果你没有将 JDK的环境变量设置好,就需要先进入JDK的bin目录下,如下图所示。)

?
2.3 查看方法的签名
????? 我们知道Java中允许方法的多态,仅仅是通过方法名并没有办法定位到一个具体的方法,因此需要一个字符串来唯一表示一个方法。但是怎么利用一个字 符串来表示方法的具体定义呢?JDK中已经准备好一个反编译工具javap,通过这个工具就可以得到类中每个属性、方法的签名。在CMD下运行javap -s -p -classpath c:\ test.Demo即可看到属性和方法的签名。如下图红色矩形框起来的字符串为方法String append(String str, int i)的签名。

?
?
在VC中调用JAVA类
?
3.1 快速调用JAVA中的函
????? 在VC中新建一个控制台程序,然后新建一个CPP文件,将下面的代码添加到该文件中。运行该文件,即可得到Demo类中String append(String str, int i)函数返回的字符串。

include “windows.h”

include “jni.h”

include

include

using namespace std;

jstring NewJString(JNIEnv *env, LPCTSTR str);
string JStringToCString (JNIEnv *env, jstring str);

int main()
{
//定义一个函数指针,下面用来指向JVM中的JNI_CreateJavaVM函数
typedef jint (WINAPI PFunCreateJavaVM)(JavaVM , void , void );

int res;
JavaVMInitArgs vm_args;
JavaVMOption options[3];
JavaVM *jvm;
JNIEnv *env;

/*设置初始化参数*/
//disable JIT,这是JNI文档中的解释,具体意义不是很清楚 ,能取哪些值也不清楚。
//从JNI文档里给的示例代码中搬过来的
options[0].optionString = "-Djava.compiler=NONE";
//设置classpath,如果程序用到了第三方的JAR包,也可以在这里面包含进来
options[1].optionString = "-Djava.class.path=.;c:\\";
//设置显示消息的类型,取值有gc、class和jni,如果一次取多个的话值之间用逗号格开,如-verbose:gc,class
//该参数可以用来观察C++调用JAVA的过程,设置该参数后,程序会在标准输出设备上打印调用的相关信息
options[2].optionString = "-verbose:NONE";

//设置版本号,版本号有JNI_VERSION_1_1,JNI_VERSION_1_2和JNI_VERSION_1_4
//选择一个根你安装的JRE版本最近的版本号即可,不过你的JRE版本一定要等于或者高于指定的版本号
vm_args.version = JNI_VERSION_1_4;
vm_args.nOptions = 3;
vm_args.options = options;
//该参数指定是否忽略非标准的参数,如果填JNI_FLASE,当遇到非标准参数时,JNI_CreateJavaVM会返回JNI_ERR
vm_args.ignoreUnrecognized = JNI_TRUE;
//加载JVM.DLL动态库
HINSTANCE hInstance = ::LoadLibrary("C:\\j2sdk1.4.2_15\\jre\\bin\\client\\jvm.dll");
if (hInstance == NULL)
{
    return false;
}
//取得里面的JNI_CreateJavaVM函数指针
PFunCreateJavaVM funCreateJavaVM = (PFunCreateJavaVM)::GetProcAddress(hInstance, "JNI_CreateJavaVM");
//调用JNI_CreateJavaVM创建虚拟机
res = (*funCreateJavaVM)(&jvm, (void**)&env, &vm_args);
if (res < 0)
{
    return -1;
}
//查找test.Demo类,返回JAVA类的CLASS对象
jclass cls = env->FindClass("test/Demo");
//根据类的CLASS对象获取该类的实例
jobject obj = env->AllocObject(cls);

//获取类中的方法,最后一个参数是方法的签名,通过javap -s -p 文件名可以获得
jmethodID mid = env->GetMethodID(cls, "append","(Ljava/lang/String;I)Ljava/lang/String;");
//构造参数并调用对象的方法
const char szTest[] = "电信";
jstring arg = NewJString(env, szTest);
jstring msg = (jstring) env->CallObjectMethod(obj, mid, arg, 12);
cout<<JStringToCString(env, msg);

//销毁虚拟机并释放动态库
jvm->DestroyJavaVM();
::FreeLibrary(hInstance);
return 0;

}

string JStringToCString (JNIEnv *env, jstring str)// (jstring str, LPTSTR desc, int desc_len)
{
if(str==NULL)
{
return “”;
}
//在VC中wchar_t是用来存储宽字节字符(UNICODE)的数据类型
int len = env->GetStringLength(str);
wchar_t *w_buffer = new wchar_t[len+1];
char *c_buffer = new char[2*len+1];
ZeroMemory(w_buffer,(len+1)*sizeof(wchar_t));
//使用GetStringChars而不是GetStringUTFChars
const jchar * jcharString = env->GetStringChars(str, 0);
wcscpy(w_buffer,jcharString);
env->ReleaseStringChars(str,jcharString);
ZeroMemory(c_buffer,(2*len+1)*sizeof(char));
/调用字符编码转换函数(Win32 API)将UNICODE转为ASCII编码格式字符串
len = WideCharToMultiByte(CP_ACP,0,w_buffer,len,c_buffer,2*len,NULL,NULL);
string cstr = c_buffer;
delete[] w_buffer;
delete[] c_buffer;

return cstr;

}

jstring NewJString(JNIEnv *env, LPCTSTR str)
{
if(!env || !str)
{
return 0;
}
int slen = strlen(str);
jchar* buffer = new jchar[slen];
int len = MultiByteToWideChar(CP_ACP,0,str,strlen(str),buffer,slen);
if(len>0 && len < slen)
{
buffer[len]=0;
}
jstring js = env->NewString(buffer,len);
delete [] buffer;
return js;
}
?
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函数导入。

???? c、JVM创建成功后,JNI_CreateJavaVM函数会传出一个JNI上下文环境对象(JNIEnv),利用该对象的相关函数就可以调用JAVA类的属性和方法了。

???? d、以上面的代码为例:先调用JNIEnv的FindClass方法,该函数传入一个参数,该参数就是java类的全局带包名的名称,如上面示例中的 test/Demo表示test包中的Demo类。这个方法会在你创建JVM时设置的classpath路径下找相应的类,找到后就会返回该类的 class对象。 Class是JAVA中的一个类,每个JAVA类都有唯一的一个静态的Class对象,Class对象包含类的相关信息。为了使FindClass方法能 找到你的类,请确保创建JVM时-Djava.class.path=参数设置正确。注意:系统环境变量中的CLASSPATH对这里创建JVM没有影 响,所以不要以为系统CLASSPATH设置好了相关路径后这里就不用设置了。

???? 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中的静态方法
?
//调用静态方法
jclass cls = env->FindClass(“test/Demo”);
jmethodID mid = env->GetStaticMethodID(cls, “getHelloWorld”,”()Ljava/lang/String;”);
jstring msg = (jstring)env->CallStaticObjectMethod(cls, mid);
cout<

include

using namespace std;
int main()
{
locale loc( “Chinese-simplified” );
//locale loc( “chs” );
//locale loc( “ZHI” );
//locale loc( “.936” );
wcout.imbue( loc );
wcout << L”中文” << endl; //若没有L,会出问题
wchar_t wch[] = {0x4E2D, 0x6587, 0x0};
//”中文”二字的Unicode编码
wcout << wch << endl;
return 0;
}

JNI提供了几个方法来实现jstring与char/wchar_t之间的转换。
jsize GetStringLength(jstring str)
const jchar *GetStringChars(jstring str, jboolean *isCopy)void ReleaseStringChars(jstring str, const jchar *chars)
此外,为了便于以UTF-8方式进行传输、存储,JNI还提供了几个操作UTF格式的方法:
jsize GetStringUTFLength(jstring str)const char* GetStringUTFChars(jstring str, jboolean isCopy)void ReleaseStringUTFChars(jstring str, const char chars)

GetStringChars返回的是Unicode格式的编码串,而GetStringUTFChars返回的是UTF-8格式的编码串。要创建一个 jstring,可以用如下方式:
jstring NewJString( JNIEnv * env, LPCTSTR str )
{
if (!env || !str)
return 0;
int slen = strlen(str);
jchar * buffer = new jchar[slen];
int len = MultiByteToWideChar(CP_ACP, 0,
str, strlen(str), buffer, slen);
if (len > 0 && len < slen)
buffer[len] = 0;
jstring js = env->NewString(buffer, len);
delete [] buffer;
return js;
}

而要将一个jstring对象转为一个char字符串数组,可以:
int JStringToChar( JNIEnv * env,
jstring str,
LPTSTR desc,
int desc_len )
{
int len = 0;
if (desc == NULL || str == NULL)
return -1;
// Check buffer size
if (env->GetStringLength(str) * 2 + 1 > desc_len)
{ return -2; }

memset(desc, 0, desc_len);
const wchar_t * w_buffer = env->GetStringChars(str, 0);
len = WideCharToMultiByte(CP_ACP, 0,
w_buffer, wcslen(w_buffer) + 1, desc, desc_len, NULL, NULL);
env->ReleaseStringChars(str, w_buffer);
if (len > 0 && len < desc_len)
desc[len] = 0;
return strlen(desc);
}
当然,按照上面的分析,你也可以直接将GetStringChars的返回结果作为wchar_t串来进行操作。或者,如果你愿意,你也可以将 GetStringUTFChars的结果通过MultiByteToWideChar转换为UCS2编码串,再通过 WideCharToMultiByte转换为多字节串。

const char* pstr = env->GetStringUTFChars(str, false);
int nLen = MultiByteToWideChar( CP_UTF8, 0, pstr, -1, NULL, NULL );
//得到UTF-8编码的字符串长度
LPWSTR lpwsz = new WCHAR[nLen];
MultiByteToWideChar( CP_UTF8, 0, pstr, -1, lpwsz, nLen );
//转换的结果是UCS2格式的编码串
int nLen1 = WideCharToMultiByte( CP_ACP,
0, lpwsz, nLen,
NULL, NULL, NULL, NULL );
LPSTR lpsz = new CHAR[nLen1];
WideCharToMultiByte( CP_ACP, 0, lpwsz, nLen, lpsz, nLen1, NULL, NULL );
//将UCS2格式的编码串转换为多字节
cout << “Out:” << lpsz << endl;
delete [] lpwsz;
delete [] lpsz;

当然,我相信很少有人想要或者需要这么做。这里需要注意一点,GetStringChars的返回值是jchar,而 GetStringUTFChars的返回值是const char*。
除了上面的办法外,当需要经常在jstring和char*之间进行转换时我们还有一个选择,那就是下面的这个类。这个类本来是一个叫 Roger S. Reynolds的老外提供的,想法非常棒,但用起来却不太灵光,因为作者将考虑的重心放在UTF格式串上,但在实际操作中,我们往往使用的却是 ACP(ANSI code page)串。下面是原作者的程序:
class UTFString {
private: UTFString ();
// Default ctor - disallowed
public:
// Create a new instance from the specified jstring
UTFString(JNIEnv* env, const jstring& str) :
mEnv (env),
mJstr (str),
mUtfChars ((char* )mEnv->GetStringUTFChars (mJstr, 0)), mString (mUtfChars) { }
// Create a new instance from the specified string
UTFString(JNIEnv* env, const string& str) :
mEnv (env),
mString (str),
mJstr (env->NewStringUTF (str.c_str ())),
mUtfChars ((char* )mEnv->GetStringUTFChars (mJstr, 0)) { }
// Create a new instance as a copy of the specified UTFString
UTFString(const UTFString& rhs) :
mEnv (rhs.mEnv),
mJstr (mEnv->NewStringUTF (rhs.mUtfChars)),
mUtfChars ((char* )mEnv->GetStringUTFChars (mJstr, 0)), mString (mUtfChars) { }
// Delete the instance and release allocated storage
~UTFString() {
mEnv->ReleaseStringUTFChars (mJstr, mUtfChars);
}
// assign a new value to this instance from the given string UTFString & operator =(const string& rhs) {
mEnv->ReleaseStringUTFChars (mJstr, mUtfChars);
mJstr = mEnv->NewStringUTF (rhs.c_str ());
mUtfChars = (char* )mEnv->GetStringUTFChars (mJstr, 0); mString = mUtfChars;
return *this;
}
// assign a new value to this instance from the given char*
UTFString & operator =(const char* ptr) {
mEnv->ReleaseStringUTFChars (mJstr, mUtfChars);
mJstr = mEnv->NewStringUTF (ptr);
mUtfChars = (char* )mEnv->GetStringUTFChars (mJstr, 0); mString = mUtfChars;
return *this;
}
// Supply operator methods for converting the UTFString to a string
// or char*, making it easy to pass UTFString arguments to functions // that require string or char* parameters.
string & GetString() { return mString; }
operator string() { return mString; }
operator const char* () {
return mString.c_str ();
}
operator jstring() {
return mJstr;
}
private:
JNIEnv* mEnv;
// The enviroment pointer for this native method.
jstring mJstr;
// A copy of the jstring object that this UTFString represents char* mUtfChars;
// Pointer to the data returned by GetStringUTFChars string mString;
// string buffer for holding the “value” of this instance };

我将它改了改:
class JNIString {private: JNIString (); // Default ctor - disallowedpublic: // Create a new instance from the specified jstring JNIString(JNIEnv* env, const jstring& str) : mEnv (env) { const jchar* w_buffer = env->GetStringChars (str, 0); mJstr = env->NewString (w_buffer, wcslen (w_buffer)); // Deep Copy, in usual case we only need // Shallow Copy as we just need this class to // provide some convenience for handling jstring mChars = new char[wcslen (w_buffer) * 2 + 1]; WideCharToMultiByte (CP_ACP, 0, w_buffer, wcslen (w_buffer) + 1, mChars, wcslen (w_buffer) * 2 + 1, NULL, NULL); env->ReleaseStringChars (str, w_buffer); mString = mChars; } // Create a new instance from the specified string JNIString(JNIEnv* env, const string& str) : mEnv (env) { int slen = str.length (); jchar* buffer = new jchar[slen]; int len = MultiByteToWideChar (CP_ACP, 0, str.c_str (), str.length (), buffer, slen); if (len > 0 && len < slen) buffer[len] = 0; mJstr = env->NewString (buffer, len); delete [] buffer; mChars = new char[str.length () + 1]; strcpy (mChars, str.c_str ()); mString.empty (); mString = str.c_str (); } // Create a new instance as a copy of the specified JNIString JNIString(const JNIString& rhs) : mEnv (rhs.mEnv) { const jchar* wstr = mEnv->GetStringChars (rhs.mJstr, 0); mJstr = mEnv->NewString (wstr, wcslen (wstr)); mEnv->ReleaseStringChars (rhs.mJstr, wstr); mChars = new char[strlen (rhs.mChars) + 1]; strcpy (mChars, rhs.mChars); mString = rhs.mString.c_str (); } // Delete the instance and release allocated storage ~JNIString() { delete [] mChars; } // assign a new value to this instance from the given string JNIString & operator =(const string& rhs) { delete [] mChars; int slen = rhs.length (); jchar* buffer = new jchar[slen]; int len = MultiByteToWideChar (CP_ACP, 0, rhs.c_str (), rhs.length (), buffer, slen); if (len > 0 && len < slen) buffer[len] = 0; mJstr = mEnv->NewString (buffer, len); delete [] buffer; mChars = new char[rhs.length () + 1]; strcpy (mChars, rhs.c_str ()); mString = rhs.c_str (); return this; } // Supply operator methods for converting the JNIString to a string // or char, making it easy to pass JNIString arguments to functions // that require string or char* parameters. string & GetString() { return mString; } operator string() { return mString; } operator const char* () { return mString.c_str (); } operator jstring() { return mJstr; }private: JNIEnv* mEnv; // The enviroment pointer for this native method. jstring mJstr; // A copy of the jstring object that this JNIString represents char* mChars; // Pointer to a ANSI code page char array string mString; // string buffer for holding the “value” of this instance (ANSI code page)};
后者除了将面向UTF编码改成了面向ANSI编码外,还去掉了operator =(const char* ptr)的定义,因为 operator =(const string& rhs)可以在需要的时候替代前者而无需任何额外编码。(因为按照C++规范,const reference可以自动转换,详见本人另一文章“关于 const reference 的几点说明”)
  如果你愿意,给JNIString再加个JNIString(JNIEnv* env, const wstring& str)和一个operator =(const wstring& rhs)操作符重载就比较完美了,:),很简单,留给用得到的朋友自己加吧。

下面是一个使用该类的例子(真正跟用于演示的code很少,大部分都是些routine code,:)):

参考资料:
1.UTF-8 and Unicode FAQ for Unix/Linuxs,http://www.cl.cam.ac.uk/~mgk25/unicode.html
其中文翻译见http://www.linuxforum.net/books/UTF-8-Unicode.html
2.深入剖析Java编程中的中文问题及建议最优解决方法,http://blog.csdn.net/abnerchai/archive/2004/04/28/18576.aspx
3.关于Java中文问题的几条分析原则,http://www-900.ibm.com/developerWorks/cn/java/l-javachinese/index.shtml
4.Java 编程技术中汉字问题的分析及解决,http://www-900.ibm.com/developerWorks/cn/java/java_chinese/index.shtml
5.深入剖析JSP和Servlet对中文的处理过程,http://blog.csdn.net/deuso/archive/2005/12/01/541511.aspx
6.宽字符标量L”xx”在VC6.0/7.0和GNU g++中的不同实现,http://blog.vckbase.com/smileonce/archive/2004/12/09/1972.html
XML Encoding,http://www.w3schools.com/xml/xml_encoding.asp

最新评论
// 编码长度可以是1~3(据说理论上最长可以到6,不懂)。

直到Unicode 2.0,Unicode还是一个很简单的编码,每个字符16位——两个字节
到了Unicode 3.0,为了支持庞大的东亚象形文字,Unicode编码空间增加为0~10FFFF,提出代理对机制(用两个w_char存储一个图形字符)来支持10000~10FFFF之间的编码(这就是UTF-16的前身)
到了Unicode 4.0,直接定义为31位空间——群、面、行、格 四级,并提出多种编码方案:UTF-7、UTF-8、UTF-16、UTF-32

UTF-8是变长编码,首字节标示了长度值,其余字节带有6位数据。由于设计得很巧妙,存在冗余位,所以可以纠错。
其携带信息:
1 Byte:7bit(7位ASCII)
2 Byte:5 + 6*1 = 11bit
3 Byte:4 + 6*2 = 16bit(16位基本语义平面字符)
4 Byte:3 + 6*3 = 21bit(21位代理对)
5 Byte:2 + 6*4 = 26bit
6 Byte:1 + 6*5 = 31bit

Ch27:JNI的crash终于搞定

今天终于搞定困扰我一周的一个问题了。

我们的算法通过jni封装,在java调用的时候总是随机的crash掉,具体的位置在jvm里面,应该可以肯定是jvm做垃圾回收的时候死掉的。但是并不知道是在回收哪块内存出的问题,所以也就无从知道死的具体原因了。我们的程序是在jni层创建了一些java对象,然后返回给java层,大体结构像下面代码一样,我只能基本判断是我们的jni层在创建对象的时候(也就是createInfo函数)出问题了,至于具体什么问题,我也不清楚。
public class Test {
public class Info {
public int x;
public int y;
public Info() {
x = 0;
y = 0;
}
}

public native Info createInfo();

// ...

}

因为我对java不是很熟悉,所以只好一边学,一边弄。最初就是在local/glbal reference这些概念上下功夫,来回的读jni的specification,也没有发现自己的问题。后期又学着使用一些java的调试工具,比如jhat啊,hpjmeter啊,但是仍然没有什么头绪。上周一周,就在这个问题上不断的尝试,也没结果。

今天终于发现了问题所在,其实说来也很简单。jni要 创建的那些返回对象,是作为内部类定义的,所以在构造的时候需要传一个外层类实例才能初始化。也就是说,虽然看上去Info类的构造函数是无参数的,但实 际上它是有一个隐含参数的,相当于Info(Test outer)。如果在java层构造这个对象,那么outer参数会被自动传入,但我们在jni层构造,就需要自己传入这个参数了。如果没有给出这个参数,jni编译运行都没有问题,但实际上,它是用了一个未知的对象(就是在栈里面的一个随机值)来作为这个outer参数的,所以当这个对象需要释放的时候(一般也就是在垃圾回收的时候)就会crash了。

现在想起来,其实这个问题我原来曾经有过一次小遭遇,那时我在使用有参数构造函数来创建一个内部嵌套类,发现构造出来的对象值是错掉的。其实就是因为少传了一个outer参数啊,但是当时我没有去解决这个问题,而是绕过问题,采用构造函数无参数,然后在创建之后,再手工给每个数据字段赋值的方法。这样虽然表面上也达到了目的,但是隐藏了问题。

事实一次次的告诉我们,遇到问题一定要解决。就算你暂时绕过这个问题,但早晚它还会出来的。

  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值