JNI_编程技术__网文整理(上)

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] <classes>

where [options] include:

       -help                 Print this help message and exit

       -classpath <path>     Path from which to load classes

       -bootclasspath <path> Path from which to load bootstrap classes

       -d< dir>              Output directory

       -o <file>             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

<classes> 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< jni.h>

/* Header for class com_ibm_course_jni_Sample1 */

#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 <string.h>

  JNIEXPORT jint JNICALL Java_com_ibm_course_jni_Sample1_intMethod

    (JNIEnv *env, jobject obj, jint num) {

     return num * num;

  }

  JNIEXPORT jboolean JNICALL Java_com_ibm_course_jni_Sample1_booleanMethod

   (JNIEnv *env, jobject obj, jboolean boolean) {

   return !boolean;

}

JNIEXPORT jstring JNICALL Java_com_ibm_course_jni_Sample1_stringMethod

   (JNIEnv *env, jobject obj, jstring string) {

     const char *str = (*env)->GetStringUTFChars(env, string, 0);

     char cap[128];

     strcpy(cap, str);

     (*env)->ReleaseStringUTFChars(env, string, str);

     return (*env)->NewStringUTF(env, strupr(cap));

}

JNIEXPORT jint JNICALL Java_com_ibm_course_jni_Sample1_intArrayMethod

   (JNIEnv *env, jobject obj, jintArray array) {

     int i, sum = 0;

     jsize len = (*env)->GetArrayLength(env, array);

     jint *body = (*env)->GetIntArrayElements(env, array, 0);

     for (i=0; i<len; i++)

     {   sum += body[i];

     }

     (*env)->ReleaseIntArrayElements(env, array, body, 0);

     return sum;

}

void main(){}

C++ 函数实现

以下是 Sample1.cpp(C++ 实现)

#include "com_ibm_course_jni_Sample1.h"

#include <string.h>

JNIEXPORT jint JNICALL Java_Sample1_intMethod

  (JNIEnv *env, jobject obj, jint num) {

   return num * num;

}

JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod

   (JNIEnv *env, jobject obj, jboolean boolean) {

   return !boolean;

}

JNIEXPORT jstring JNICALL Java_Sample1_stringMethod

   (JNIEnv *env, jobject obj, jstring string) {

     const char *str = env->GetStringUTFChars(string, 0);

     char cap[128];

     strcpy(cap, str);

     env->ReleaseStringUTFChars(string, str);

     return env->NewStringUTF(strupr(cap));

}

JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod

   (JNIEnv *env, jobject obj, jintArray array) {

     int i, sum = 0;

     jsize len = env->GetArrayLength(array);

     jint *body = env->GetIntArrayElements(array, 0);

     for (i=0; i<len; i++)

     {   sum += body[i];

     }

     env->ReleaseIntArrayElements(array, body, 0);

     return sum;

}

void main(){}

C 和 C++ 函数实现的比较

唯一的差异在于用来访问 JNI 函数的方法。在 C 中,JNI 函数调用由“(*env)->”作前缀,目的是为了取出函数指针所引用的值。在 C++ 中,JNIEnv 类拥有处理函数指针查找的内联成员函数。下面将说明这个细微的差异,其中,这两行代码访问同一函数,但每种语言都有各自的语法。

C 语法:jsize len = (*env)->GetArrayLength(env,array);

C++ 语法:jsize len =env->GetArrayLength(array);

步骤 5:创建共享库文件

接下来,我们创建包含本机代码的共享库文件。大多数 C 和 C++ 编译器除了可以创建机器代码可执行文件以外,也可以创建共享库文件。用来创建共享库文件的命令取决于您使用的编译器。下面是在 Windows 和 Solaris 系统上执行的命令。

Windows:cl -Ic:/jdk/include -Ic:/jdk/include/win32 -LD Sample1.c -FeSample1.dll

Solaris:cc -G -I/usr/local/jdk/include -I/user/local/jdk/include/solaris Sample1.c-o Sample1.so

步骤 6:运行 Java 程序

最后一步是运行 Java 程序,并确保代码正确工作。因为必须在 Java 虚拟机中执行所有 Java 代码,所以需要使用 Java 运行时环境。完成这一步的方法之一是使用 java,它是随 SDK 一起提供的 Java 解释器。所使用的命令是:

   

java com.ibm.course.jni.Sample1

当运行 Sample1.class 程序时,应该获得下列结果:

PROMPT>java Sample1

intMethod: 25

booleanMethod: false

stringMethod: JAVA

intArrayMethod: 33

PROMPT>

故障排除

当使用 JNI 从 Java 程序访问本机代码时,您会遇到许多问题。您会遇到的三个最常见的错误是:

  • 无法找到动态链接。它所产生的错误消息是:java.lang.UnsatisfiedLinkError。这通常指无法找到共享库,或者无法找到共享库内特定的本机方法。
  • 无法找到共享库文件。当用 System.loadLibrary(String     libname) 方法(参数是文件名)装入库文件时,请确保文件名拼写正确以及没有指定扩展名。还有,确保库文件的位置在类路径中,从而确保 JVM 可以访问该库文件。
  • 无法找到具有指定说明的方法。确保您的C/C++ 函数实现拥有与头文件中的函数说明相同的说明。

从 Java 调用 C 或 C++ 本机代码(虽然不简单)是 Java 平台中一种良好集成的功能。虽然 JNI 支持 C 和 C++,但 C++ 接口更清晰一些并且通常比 C 接口更可取。

正如您已经看到的,调用 C 或 C++ 本机代码需要赋予函数特殊的名称,并创建共享库文件。当利用现有代码库时,更改代码通常是不可取的。要避免这一点,在 C++ 中,通常创建代理代码或代理类,它们有专门的 JNI 所需的命名函数。然后,这些函数可以调用底层库函数,这些库函数的说明和实现保持不变。

Chap6: JNI传递返回值

作为主调方的Java源程序TestJNI.java如下。

代码清单15-4 在Linux平台上调用C函数的例程——TestJNI.java

1.       public class TestJNI

2.       {

3.          static

4.          {

5.            System.loadLibrary("testjni");//载入静态库,test函数在其中实现

6.          }

7.     

8.          private native void testjni(); //声明本地调用

9.        

10.       public void test()

11.       {

12.         testjni();

13.       }

14.  

15.       public static void main(String args[])

16.       {

17.         TestJNI haha = new TestJNI();

18.         haha.test();

19.       }

20.    }

TestJNI.java声明从libtestjni.so(注意Linux平台的动态链接库文件的扩展名是.so)中调用函数testjni()。

在Linux平台上,遵循JNI规范的动态链接库文件名必须以“lib”开头。例如在上面的Java程序中指定的库文件名为“testjni”,则实际的库文件应该命名为“libtestjni.so”。

编译TestJNI.java,并为C程序生成头文件:

javac TestJNI.java

javah TestJNI

提供testjni()函数的testjni.c源文件如下。

代码清单15-5 在Linux平台上调用C函数的例程——testjni.c

       #include <stdio.h>

      #include <TestJNI.h>  

     JNIEXPORT void JNICALL Java_TestJNI_testjni(JNIEnv *env, jobject obj){

      printf("haha---------go into c!!!/n");

     }

编写Makefile文件如下,JDK安装的位置请读者自行调整:

libtestjni.so:testjni.o

     gcc -rdynamic -shared -o libtestjni.so testjni.o

testjni.o:testjni.c TestJNI.h

     gcc -c testjni.c -I./-I/usr/java/jdk1.6.0_00/include-I/usr/java/jdk1.6.0_00/include/linux

在Makefile文件中,我们描述了最终的 libtestjin.so依赖于目标文件testjni.o,而testjni.o则依赖于testjni.c源文件和TestJNI.h头文件。请注 意,我们在将testjni.o连接成动态链接库文件时使用了“-rdynamic”选项。

执行make命令编译testjni.c。Linux平台和在Windows平台上类似,有3种方法可以让Java程序找到并装载动态链接库文件。

— 将动态链接库文件放置在当前路径下。

— 将动态链接库文件放置在LD_LIBRARY_PATH环境变量所指向的路径下。注意这一点和Windows平台稍有区别,Windows平台参考PATH环境变量。

— 在启动JVM时指定选项“-Djava.library.path”,将动态链接库文件放置在该选项所指向的路径下。

从下一节开始,我们开始接触到在JNI框架内Java调用C程序的一些高级话题,包括如何传递参数、如何传递数组、如何传递对象等。

各种类型数据的传递是跨平台、跨语言互操作的永恒话题,更复杂的操作其实都可以分解为各种 基本数据类型的操作。只有掌握了基于各种数据类型的互操作,才能称得上掌握了JNI开发。从下一节开始,环境和步骤不再是阐述的重点,将不再花费专门的篇 幅,例程中的关键点将成为我们关注的焦点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值